Skip to content

Multi Select

omar edited this page Dec 21, 2023 · 14 revisions

Multi-Select

  • This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) and supports a clipper being used. Handling this manually and correctly is tricky, this is why we provide the functionality. If you don't need SHIFT+Mouse/Keyboard range-select + clipping, you could technically implement a simple form of multi-selection yourself, by reacting to click/presses on Selectable() items.
  • TreeNode() and Selectable() are supported but custom widgets may use it as well.
  • In the spirit of Dear ImGui design, your code owns actual selection data. This is designed to allow all kinds of selection storage you may use in your application e.g. external selection (set/map/hash), intrusive selection (bool inside your objects) etc.
  • The work involved to deal with multi-selection differs whether you want to only submit visible items and clip others, or submit all items regardless of their visibility. Clipping items is more efficient and will allow you to deal with large lists (1k~100k items). See "Usage flow" section below for details. If you are not sure, always start without clipping! You can work your way to the optimized version afterwards.

Usage Flow

  • (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
  • (2) [If using clipper] Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 6.
  • (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work.
  • (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls.
  • (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result.
  • (6) Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 2.

If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is perfectly fine if you honor those steps without a clipper.

With ImGuiSelectionBasicStorage

ImGuiSelectionBasicStorage is an optional helper to store multi-selection state + apply multi-selection requests.

  • Used by our demos and provided as a convenience to easily implement basic multi-selection.
  • USING THIS IS NOT MANDATORY. This is only a helper and not a required API. Advanced users are likely to implement their own.

Minimum pseudo-code example using this helper:

static vector<MyItem> items;                  // Your items
static ImGuiSelectionBasicStorage selection;  // Your selection
selection.AdapterData = (void*)&items;        // Setup adapter so selection.ApplyRequests() function can convert indexes to identifiers.
selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((vector<MyItem>*)self->AdapterData))[idx].ID; };

ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_None);
selection.ApplyRequests(ms_io, items.Size);
for (int idx = 0; idx < items.Size; idx++)
{
    bool item_is_selected = selection.Contains(items[idx].ID);
    ImGui::SetNextItemSelectionUserData(idx);
    ImGui::Selectable(label, item_is_selected);
}
ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, items.Size);

To store a multi-selection, in your real application you could:

  • A) Use this helper as a convenience. We use our simple key->value ImGuiStorage as a std::set replacement.
  • B) Use your own external storage: e.g. std::set, std::vector, interval trees, etc.
  • C) Use intrusively stored selection (e.g. 'bool IsSelected' inside objects). Not recommended because you can't have multiple views over same objects. Also some features requires to provide selection size, which with this strategy requires additional work.

Our BeginMultiSelect() api/system doesn't make assumption about:

  • how you want to identify items in multi-selection API? (Indices or Custom Ids or Pointers? Indices are better: easy to iterate/interpolate)
  • how you want to store persistent selection data? (Indices or Custom Ids or Pointers? Custom Ids is better: as selection can persist)

In ImGuiSelectionBasicStorage we:

  • always use indices in the multi-selection API (passed to SetNextItemSelectionUserData(), retrieved in ImGuiMultiSelectIO)
  • use the AdapterIndexToStorageId() indirection layer to abstract how persistent selection data is derived from an index.
  • in some cases we use Index as custom identifier (default implementation returns Index cast as Identifier): only valid for a never changing item list.
  • in some cases we read an ID from some custom item data structure (better, and closer to what you would do in your codebase)

Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection. When your application settles on a choice, you may want to get rid of this indirection layer and do your own thing.

Clone this wiki locally