From ad31d369c3ff38a8bc1de768c67aa57031d9c5b9 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 21 Dec 2019 23:21:23 +0100 Subject: [PATCH] RangeSelect/MultiSelect: Demo sharing selection helper code. Fixed static analyzer warnings. --- imgui.h | 8 ++++--- imgui_demo.cpp | 56 +++++++++++++++++++++++++++++++---------------- imgui_widgets.cpp | 10 ++++----- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/imgui.h b/imgui.h index 5788c4ae..37a77662 100644 --- a/imgui.h +++ b/imgui.h @@ -2662,11 +2662,13 @@ struct ImColor #define IMGUI_HAS_MULTI_SELECT // Multi-Select/Range-Select WIP branch // <-- This is currently _not_ in the top of imgui.h to prevent merge conflicts. // Flags for BeginMultiSelect(). -// This system is designed to allow mouse/keyboard multi-selection, including support for range-selection (SHIFT + click) which is difficult to re-implement manually. -// If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect (which is provided for consistency and flexibility), the whole BeginMultiSelect() system -// becomes largely overkill as you can handle single-selection in a simpler manner by just calling Selectable() and reacting on clicks yourself. +// This system is designed to allow mouse/keyboard multi-selection, including support for range-selection (SHIFT + click), +// which is difficult to re-implement manually. If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect +// (which is provided for consistency and flexibility), the whole BeginMultiSelect() system becomes largely overkill as +// you can handle single-selection in a simpler manner by just calling Selectable() and reacting on clicks yourself. enum ImGuiMultiSelectFlags_ { + ImGuiMultiSelectFlags_None = 0, ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0, ImGuiMultiSelectFlags_NoUnselect = 1 << 1, // Disable unselecting items with CTRL+Click, CTRL+Space etc. ImGuiMultiSelectFlags_NoSelectAll = 1 << 2, // Disable CTRL+A shortcut to set RequestSelectAll diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7dd5c983..25318cb0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2739,6 +2739,38 @@ static void ShowDemoWindowWidgets() } } +// [Advanced] Helper class to simulate storage of a multi-selection state, used by the advanced multi-selection demos. +// We use ImGuiStorage (simple key->value storage) to avoid external dependencies but it's probably not optimal. +// To store a single-selection: +// - You only need a single variable and don't need any of this! +// To store a multi-selection, in your real application you could: +// - Use intrusively stored selection (e.g. 'bool IsSelected' inside your object). This is by far the simplest +// way to store your selection data, but it means you cannot have multiple simultaneous views over your objects. +// This is what may of the simpler demos in this file are using (so they are not using this class). +// - Otherwise, any externally stored unordered_set/set/hash/map/interval trees (storing indices, objects id, etc.) +// are generally appropriate. Even a large array of bool might work for you... +struct ExampleSelectionData +{ + ImGuiStorage Storage; + int SelectedCount; // Number of selected items (storage will keep this updated) + + ExampleSelectionData() { Clear(); } + void Clear() { Storage.Clear(); SelectedCount = 0; } + bool GetSelected(int id) const { return Storage.GetInt((ImGuiID)id) != 0; } + void SetSelected(int id, bool v) { int* p_int = Storage.GetIntRef((ImGuiID)id); if (*p_int == (int)v) return; SelectedCount = v ? (SelectedCount + 1) : (SelectedCount - 1); *p_int = (bool)v; } + int GetSelectedCount() const { return SelectedCount; } + + // 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). + // If your selection system is storing selection using object ID and you want to support Shift+Click range-selection, + // you will need a way to iterate from one object to another given the ID you use. + // You are likely to need some kind of data structure to convert 'view index' from/to 'ID'. + // FIXME-MULTISELECT: Would be worth providing a demo of doing this. + // FIXME-MULTISELECT: SetRange() is currently very inefficient since it doesn't take advantage of the fact that ImGuiStorage stores sorted key. + void SetRange(int a, int b, bool v) { if (b < a) { int tmp = b; b = a; a = tmp; } for (int n = a; n <= b; n++) SetSelected(n, v); } + void SelectAll(int count) { Storage.Data.resize(count); for (int n = 0; n < count; n++) Storage.Data[n] = ImGuiStoragePair((ImGuiID)n, 1); SelectedCount = count; } // This could be using SetRange() but this is faster. +}; + static void ShowDemoWindowMultiSelect() { IMGUI_DEMO_MARKER("Widgets/Selection State"); @@ -2783,22 +2815,8 @@ static void ShowDemoWindowMultiSelect() if (ImGui::TreeNode("Multiple Selection (Full)")) { // Demonstrate holding/updating multi-selection data and using the BeginMultiSelect/EndMultiSelect API to support range-selection and clipping. - // In this demo we use ImGuiStorage (simple key->value storage) to avoid external dependencies but it's probably not optimal. - // In your real code you could use e.g std::unordered_set<> or your own data structure for storing selection. - // If you don't mind being limited to one view over your objects, the simplest way is to use an intrusive selection (e.g. store bool inside object, as used in examples above). - // Otherwise external set/hash/map/interval trees (storing indices, etc.) may be appropriate. - struct MySelection - { - ImGuiStorage Storage; - void Clear() { Storage.Clear(); } - void SelectAll(int count) { Storage.Data.reserve(count); Storage.Data.resize(0); for (int n = 0; n < count; n++) Storage.Data.push_back(ImGuiStoragePair((ImGuiID)n, 1)); } - void SetRange(int a, int b, int sel) { if (b < a) { int tmp = b; b = a; a = tmp; } for (int n = a; n <= b; n++) Storage.SetInt((ImGuiID)n, sel); } - bool GetSelected(int id) const { return Storage.GetInt((ImGuiID)id) != 0; } - void SetSelected(int id, bool v) { SetRange(id, id, v ? 1 : 0); } - }; - static int selection_ref = 0; // Selection pivot (last clicked item, we need to preserve this to handle range-select) - static MySelection selection; + static ExampleSelectionData selection; const char* random_names[] = { "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper", @@ -2812,8 +2830,8 @@ static void ShowDemoWindowMultiSelect() if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20))) { - ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(0, (void*)(intptr_t)selection_ref, selection.GetSelected((int)selection_ref)); - if (multi_select_data->RequestClear) { selection.Clear(); } + ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_None, (void*)(intptr_t)selection_ref, selection.GetSelected((int)selection_ref)); + if (multi_select_data->RequestClear) { selection.Clear(); } if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); } ImGuiListClipper clipper; clipper.Begin(COUNT); @@ -2836,9 +2854,9 @@ static void ShowDemoWindowMultiSelect() multi_select_data = ImGui::EndMultiSelect(); selection_ref = (int)(intptr_t)multi_select_data->RangeSrc; ImGui::EndListBox(); - if (multi_select_data->RequestClear) { selection.Clear(); } + if (multi_select_data->RequestClear) { selection.Clear(); } if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); } - if (multi_select_data->RequestSetRange) { selection.SetRange((int)(intptr_t)multi_select_data->RangeSrc, (int)(intptr_t)multi_select_data->RangeDst, multi_select_data->RangeValue ? 1 : 0); } + if (multi_select_data->RequestSetRange) { selection.SetRange((int)(intptr_t)multi_select_data->RangeSrc, (int)(intptr_t)multi_select_data->RangeDst, multi_select_data->RangeValue ? 1 : 0); } } ImGui::TreePop(); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f5980419..6c621777 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7131,10 +7131,10 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) // Auto-select as you navigate a list if (g.NavJustMovedToId == id) { - if (!g.IO.KeyCtrl) - selected = pressed = true; - else if (g.IO.KeyCtrl && g.IO.KeyShift) + if (is_ctrl && is_shift) pressed = true; + else if (!is_ctrl) + selected = pressed = true; } // Right-click handling: this could be moved at the Selectable() level. @@ -7203,9 +7203,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } else if (input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) { - if (!is_multiselect) + if (is_multiselect && is_shift && !is_ctrl) ms->Out.RequestClear = true; - else if (is_shift && !is_ctrl && is_multiselect) + else if (!is_multiselect) ms->Out.RequestClear = true; } }