From c9d8068bbe24e32505a990984566b7e1aaf0516b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 31 Aug 2023 15:50:01 +0200 Subject: [PATCH] RangeSelect/MultiSelect: Added ImGuiMultiSelectFlags_SelectOnClickRelease to allow dragging an unselected item without altering selection + update drag and drop demo. --- imgui.h | 2 ++ imgui_demo.cpp | 63 ++++++++++++++++++++++++++++++----------------- imgui_widgets.cpp | 3 +-- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/imgui.h b/imgui.h index 37183d6c..2c41886d 100644 --- a/imgui.h +++ b/imgui.h @@ -2673,6 +2673,8 @@ enum ImGuiMultiSelectFlags_ ImGuiMultiSelectFlags_ClearOnEscape = 1 << 2, // Clear selection when pressing Escape while scope is focused. ImGuiMultiSelectFlags_ClearOnClickWindowVoid= 1 << 3, // Clear selection when clicking on empty location within host window (use if BeginMultiSelect() covers a whole window) //ImGuiMultiSelectFlags_ClearOnClickRectVoid= 1 << 4, // Clear selection when clicking on empty location within rectangle covered by selection scope (use if multiple BeginMultiSelect() are used in the same host window) + ImGuiMultiSelectFlags_SelectOnClick = 1 << 5, // Apply selection on mouse down when clicking on unselected item. (Default) + ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 6, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. }; // Multi-selection system diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d774ae4a..433305fd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3318,6 +3318,7 @@ static void ShowDemoWindowMultiSelect() ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll); ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape); ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickWindowVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickWindowVoid); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease); ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); // Initialize default list with 1000 items. static ImVector items; @@ -3383,11 +3384,11 @@ static void ShowDemoWindowMultiSelect() // IMPORTANT: for deletion refocus to work we need object ID to be stable, // aka not depend on their index in the list. Here we use our persistent item_id // instead of index to build a unique ID that will persist. - // (If we used PushID(n) instead, focus wouldn't be restored correctly after deletion). + // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion). ImGui::PushID(item_id); // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part - // of the selection scope doesn't erroneously alter our selection (FIXME-TESTS: Add a test for that!). + // of the selection scope doesn't erroneously alter our selection. if (show_color_button) { ImU32 dummy_col = (ImU32)((unsigned int)n * 0xC250B74B) | IM_COL32_A_MASK; @@ -3395,39 +3396,57 @@ static void ShowDemoWindowMultiSelect() ImGui::SameLine(); } + // Submit item bool item_is_selected = selection.Contains((ImGuiID)n); + bool item_is_open = false; ImGui::SetNextItemSelectionUserData(n); if (widget_type == WidgetType_Selectable) { - ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_None; - ImGui::Selectable(label, item_is_selected, selectable_flags); - if (item_curr_idx_to_focus == n) - ImGui::SetKeyboardFocusHere(-1); - - if (use_drag_drop && ImGui::BeginDragDropSource()) - { - ImGui::Text("(Dragging %d items)", selection.GetSize()); - ImGui::EndDragDropSource(); - } + ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_None); } else if (widget_type == WidgetType_TreeNode) { - ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth; - tree_node_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; if (item_is_selected) tree_node_flags |= ImGuiTreeNodeFlags_Selected; - bool open = ImGui::TreeNodeEx(label, tree_node_flags); - if (item_curr_idx_to_focus == n) - ImGui::SetKeyboardFocusHere(-1); - if (use_drag_drop && ImGui::BeginDragDropSource()) + item_is_open = ImGui::TreeNodeEx(label, tree_node_flags); + } + + // Focus (for after deletion) + if (item_curr_idx_to_focus == n) + ImGui::SetKeyboardFocusHere(-1); + + // Drag and Drop + if (use_drag_drop && ImGui::BeginDragDropSource()) + { + // Write payload with full selection OR single unselected item (only possible with ImGuiMultiSelectFlags_SelectOnClickRelease) + if (ImGui::GetDragDropPayload() == NULL) { - ImGui::Text("(Dragging %d items)", selection.GetSize()); - ImGui::EndDragDropSource(); + ImVector payload_items; + if (!item_is_selected) + payload_items.push_back(item_id); + else + for (const ImGuiStoragePair& pair : selection.Storage.Data) + if (pair.val_i) + payload_items.push_back((int)pair.key); + ImGui::SetDragDropPayload("MULTISELECT_DEMO_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); } - if (open) - ImGui::TreePop(); + + // Display payload content in tooltip + const ImGuiPayload* payload = ImGui::GetDragDropPayload(); + const int* payload_items = (int*)payload->Data; + const int payload_count = (int)payload->DataSize / (int)sizeof(payload_items[0]); + if (payload_count == 1) + ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]); + else + ImGui::Text("Dragging %d objects", payload_count); + + ImGui::EndDragDropSource(); } + if (widget_type == WidgetType_TreeNode && item_is_open) + ImGui::TreePop(); + // Right-click: context menu if (ImGui::BeginPopupContextItem()) { diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 092efd84..b862c7e0 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7189,10 +7189,9 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags // Alter button behavior flags // To handle drag and drop of multiple items we need to avoid clearing selection on click. // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. - // FIXME-MULTISELECT: Consider opt-in for drag and drop behavior in ImGuiMultiSelectFlags? ImGuiButtonFlags button_flags = *p_button_flags; button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; - if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) + if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; else button_flags |= ImGuiButtonFlags_PressedOnClickRelease;