1
0
Fork 0
mirror of https://github.com/ocornut/imgui.git synced 2026-01-11 00:04:24 +00:00

MultiSelect: WIP range-select (#1861) (rebased six millions times)

This commit is contained in:
omar 2019-02-01 12:22:57 +01:00 committed by ocornut
parent c2d21ab04f
commit 554db6bc0f
5 changed files with 533 additions and 59 deletions

View file

@ -72,6 +72,7 @@ Index of this file:
// [SECTION] Demo Window / ShowDemoWindow()
// - ShowDemoWindow()
// - sub section: ShowDemoWindowWidgets()
// - sub section: ShowDemoWindowMultiSelect()
// - sub section: ShowDemoWindowLayout()
// - sub section: ShowDemoWindowPopups()
// - sub section: ShowDemoWindowTables()
@ -214,6 +215,7 @@ static void ShowExampleMenuFile();
// We split the contents of the big ShowDemoWindow() function into smaller functions
// (because the link time of very large functions grow non-linearly)
static void ShowDemoWindowWidgets();
static void ShowDemoWindowMultiSelect();
static void ShowDemoWindowLayout();
static void ShowDemoWindowPopups();
static void ShowDemoWindowTables();
@ -251,6 +253,7 @@ void* GImGuiDemoMarkerCallbackUserData = NULL;
//-----------------------------------------------------------------------------
// - ShowDemoWindow()
// - ShowDemoWindowWidgets()
// - ShowDemoWindowMultiSelect()
// - ShowDemoWindowLayout()
// - ShowDemoWindowPopups()
// - ShowDemoWindowTables()
@ -1371,37 +1374,6 @@ static void ShowDemoWindowWidgets()
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Single Selection");
if (ImGui::TreeNode("Selection State: Single Selection"))
{
static int selected = -1;
for (int n = 0; n < 5; n++)
{
char buf[32];
sprintf(buf, "Object %d", n);
if (ImGui::Selectable(buf, selected == n))
selected = n;
}
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection");
if (ImGui::TreeNode("Selection State: Multiple Selection"))
{
HelpMarker("Hold CTRL and click to select multiple items.");
static bool selection[5] = { false, false, false, false, false };
for (int n = 0; n < 5; n++)
{
char buf[32];
sprintf(buf, "Object %d", n);
if (ImGui::Selectable(buf, selection[n]))
{
if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held
memset(selection, 0, sizeof(selection));
selection[n] ^= 1;
}
}
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line");
if (ImGui::TreeNode("Rendering more items on the same line"))
{
@ -1461,6 +1433,15 @@ static void ShowDemoWindowWidgets()
if (winning_state)
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f)));
static float spacing = 0.0f;
ImGui::PushItemWidth(100);
ImGui::SliderFloat("SelectableSpacing", &spacing, 0, 20, "%.0f");
ImGui::SameLine(); HelpMarker("Selectable cancel out the regular spacing between items by extending itself by ItemSpacing/2 in each direction.\nThis has two purposes:\n- Avoid the gap between items so the mouse is always hitting something.\n- Avoid the gap between items so range-selected item looks connected.\nBy changing SelectableSpacing we can enforce spacing between selectables.");
ImGui::PopItemWidth();
ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 8));
ImGui::PushStyleVar(ImGuiStyleVar_SelectableSpacing, ImVec2(spacing, spacing));
for (int y = 0; y < 4; y++)
for (int x = 0; x < 4; x++)
{
@ -1479,8 +1460,10 @@ static void ShowDemoWindowWidgets()
ImGui::PopID();
}
ImGui::PopStyleVar(2);
if (winning_state)
ImGui::PopStyleVar();
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
@ -1509,6 +1492,8 @@ static void ShowDemoWindowWidgets()
ImGui::TreePop();
}
ShowDemoWindowMultiSelect();
// To wire InputText() with std::string or any other custom string type,
// see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file.
IMGUI_DEMO_MARKER("Widgets/Text Input");
@ -2785,6 +2770,113 @@ static void ShowDemoWindowWidgets()
}
}
static void ShowDemoWindowMultiSelect()
{
IMGUI_DEMO_MARKER("Widgets/Selection State");
if (ImGui::TreeNode("Selection State"))
{
HelpMarker("Selections can be built under Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data.");
IMGUI_DEMO_MARKER("Widgets/Selection State/Single Selection");
if (ImGui::TreeNode("Single Selection"))
{
static int selected = -1;
for (int n = 0; n < 5; n++)
{
char buf[32];
sprintf(buf, "Object %d", n);
if (ImGui::Selectable(buf, selected == n))
selected = n;
}
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selection State/Multiple Selection (Basic)");
if (ImGui::TreeNode("Multiple Selection (Basic)"))
{
HelpMarker("Hold CTRL and click to select multiple items.");
static bool selection[5] = { false, false, false, false, false };
for (int n = 0; n < 5; n++)
{
char buf[32];
sprintf(buf, "Object %d", n);
if (ImGui::Selectable(buf, selection[n]))
{
if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held
memset(selection, 0, sizeof(selection));
selection[n] ^= 1;
}
}
ImGui::TreePop();
}
IMGUI_DEMO_MARKER("Widgets/Selection State/Multiple Selection (Full)");
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;
const char* random_names[] =
{
"Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
"Bitter Gourd", "Bok Choy", "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", "Calabash", "Capers", "Carrot", "Cassava",
"Cauliflower", "Celery", "Celery Root", "Celcuce", "Chayote", "Celtuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber"
};
int COUNT = 1000;
HelpMarker("Hold CTRL and click to select multiple items. Hold SHIFT to select a range.");
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int*)&ImGui::GetIO().ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
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(); }
if (multi_select_data->RequestSelectAll) { selection.SelectAll(COUNT); }
ImGuiListClipper clipper;
clipper.Begin(COUNT);
while (clipper.Step())
{
if (clipper.DisplayStart > (int)selection_ref)
multi_select_data->RangeSrcPassedBy = true;
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
{
ImGui::PushID(n);
char label[64];
sprintf(label, "Object %05d (category: %s)", n, random_names[n % IM_ARRAYSIZE(random_names)]);
bool item_is_selected = selection.GetSelected(n);
ImGui::SetNextItemSelectionUserData(n);
if (ImGui::Selectable(label, item_is_selected))
selection.SetSelected(n, !item_is_selected);
ImGui::PopID();
}
}
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->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); }
}
ImGui::TreePop();
}
ImGui::TreePop();
}
}
static void ShowDemoWindowLayout()
{
IMGUI_DEMO_MARKER("Layout");
@ -6771,6 +6863,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("SelectableSpacing", (float*)&style.SelectableSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SameLine(); HelpMarker("SelectableSpacing must be < ItemSpacing.\nSelectables display their highlight after canceling out the effect of ItemSpacing, so they can be look tightly packed. This setting allows to enforce spacing between them.");
ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f");
ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f");