RangeSelect/MultiSelect: Demo sharing selection helper code. Fixed static analyzer warnings.

This commit is contained in:
omar 2019-12-21 23:21:23 +01:00 committed by ocornut
parent 0d73bf3755
commit 72da877c4c
4 changed files with 49 additions and 29 deletions

View File

@ -1732,11 +1732,13 @@ enum ImGuiMouseCursor_
}; };
// Flags for BeginMultiSelect(). // 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. // This system is designed to allow mouse/keyboard multi-selection, including support for range-selection (SHIFT + click),
// If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect (which is provided for consistency and flexibility), the whole BeginMultiSelect() system // which is difficult to re-implement manually. If you disable multi-selection with ImGuiMultiSelectFlags_NoMultiSelect
// becomes largely overkill as you can handle single-selection in a simpler manner by just calling Selectable() and reacting on clicks yourself. // (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_ enum ImGuiMultiSelectFlags_
{ {
ImGuiMultiSelectFlags_None = 0,
ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0, ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0,
ImGuiMultiSelectFlags_NoUnselect = 1 << 1, // Disable unselecting items with CTRL+Click, CTRL+Space etc. 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_NoSelectAll = 1 << 2 // Disable CTRL+A shortcut to set RequestSelectAll

View File

@ -554,6 +554,38 @@ void ImGui::ShowDemoWindow(bool* p_open)
ImGui::End(); ImGui::End();
} }
// [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] = ImGuiStorage::ImGuiStoragePair((ImGuiID)n, 1); SelectedCount = count; } // This could be using SetRange() but this is faster.
};
static void ShowDemoWindowWidgets() static void ShowDemoWindowWidgets()
{ {
IMGUI_DEMO_MARKER("Widgets"); IMGUI_DEMO_MARKER("Widgets");
@ -1218,22 +1250,8 @@ static void ShowDemoWindowWidgets()
if (ImGui::TreeNode("Selection State: Multiple Selection (Full)")) if (ImGui::TreeNode("Selection State: Multiple Selection (Full)"))
{ {
// Demonstrate holding/updating multi-selection data and using the BeginMultiSelect/EndMultiSelect API to support range-selection and clipping. // 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(ImGuiStorage::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 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[] = const char* random_names[] =
{ {
"Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper", "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
@ -1247,7 +1265,7 @@ static void ShowDemoWindowWidgets()
if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20))) 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)); 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->RequestClear) { selection.Clear(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); } if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); }
ImGuiListClipper clipper; ImGuiListClipper clipper;

View File

@ -1090,11 +1090,11 @@ enum ImGuiNextItemDataFlags_
struct ImGuiNextItemData struct ImGuiNextItemData
{ {
ImGuiNextItemDataFlags Flags; ImGuiNextItemDataFlags Flags;
float Width; // Set by SetNextItemWidth() float Width; // Set by SetNextItemWidth()
ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging) ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging)
ImGuiCond OpenCond; ImGuiCond OpenCond;
bool OpenVal; // Set by SetNextItemOpen() bool OpenVal; // Set by SetNextItemOpen()
ImGuiID MultiSelectScopeId; ImGuiID MultiSelectScopeId; // Set by SetNextItemMultiSelectData()
void* MultiSelectData; void* MultiSelectData;
ImGuiNextItemData() { memset(this, 0, sizeof(*this)); } ImGuiNextItemData() { memset(this, 0, sizeof(*this)); }
@ -1403,7 +1403,7 @@ struct ImGuiOldColumns
// [SECTION] Multi-select support // [SECTION] Multi-select support
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#define IMGUI_HAS_MULTI_SELECT 1 //#define IMGUI_HAS_MULTI_SELECT 1
#ifdef IMGUI_HAS_MULTI_SELECT #ifdef IMGUI_HAS_MULTI_SELECT
struct IMGUI_API ImGuiMultiSelectState struct IMGUI_API ImGuiMultiSelectState

View File

@ -6494,10 +6494,10 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
// Auto-select as you navigate a list // Auto-select as you navigate a list
if (g.NavJustMovedToId == id) if (g.NavJustMovedToId == id)
{ {
if (!g.IO.KeyCtrl) if (is_ctrl && is_shift)
selected = pressed = true;
else if (g.IO.KeyCtrl && g.IO.KeyShift)
pressed = true; pressed = true;
else if (!is_ctrl)
selected = pressed = true;
} }
// Right-click handling: this could be moved at the Selectable() level. // Right-click handling: this could be moved at the Selectable() level.
@ -6566,9 +6566,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
} }
else if (input_source == ImGuiInputSource_Nav) else if (input_source == ImGuiInputSource_Nav)
{ {
if (!is_multiselect) if (is_multiselect && is_shift && !is_ctrl)
state->Out.RequestClear = true; state->Out.RequestClear = true;
else if (is_shift && !is_ctrl && is_multiselect) else if (!is_multiselect)
state->Out.RequestClear = true; state->Out.RequestClear = true;
} }
} }