How to Implement Multi-Select Functionality for Lists and Items in Dear ImGui
Dear ImGui provides a dedicated BeginMultiSelect() API that creates an ImGuiMultiSelectIO context to handle Ctrl-click toggling, Shift-click range selection, and box-select drag operations when rendering items with Selectable() and the ImGuiSelectableFlags_SelectOnNav flag.
The ocornut/imgui repository includes a comprehensive multi-select system that manages selection state, keyboard navigation, and complex selection patterns through a structured begin/end API. This implementation, found in imgui.h and imgui_widgets.cpp, allows developers to build list widgets supporting multiple selection modalities without manually tracking keyboard modifiers or drag rectangles.
Understanding the Dear ImGui Multi-Select Architecture
The multi-select API operates through a temporary state object stored in the current ImGui context (g.CurrentMultiSelect). When you call ImGui::BeginMultiSelect(), the function returns an ImGuiMultiSelectIO* pointer that tracks selection requests, range operations, and box-select geometry for the duration of the block.
According to the implementation in imgui_widgets.cpp (lines 7395-7587), Selectable() widgets automatically communicate with the active multi-select context when the ImGuiSelectableFlags_SelectOnNav flag is present. This flag ensures that keyboard or gamepad navigation automatically selects focused items, while mouse interactions generate toggle requests stored in the IO structure.
The system separates input detection from state application. While the block is active, ImGui records selection requests (such as "select items 3-7" from a Shift-click). After rendering all items, you process these requests using ImGui::ApplyRequests() and synchronize your external storage with the ms_io->Selection bit array.
Step-by-Step Implementation Guide
Step 1: Initialize the Multi-Select Context
Call ImGui::BeginMultiSelect() before drawing your list items. This function accepts scope flags, your current selection size, and the total item count:
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
ImGuiMultiSelectFlags_ScopeWindow, // Enables box-select across the window
selection.Size, // Current selection count (-1 if unknown)
items.Size); // Total items (-1 if unknown)
The ImGuiMultiSelectFlags_ScopeWindow flag establishes that box-select operations and "click outside to clear" behavior apply to the entire window hosting the widget. Alternatively, ImGuiMultiSelectFlags_ScopeRect limits these interactions to the rectangular area between your BeginMultiSelect and EndMultiSelect calls, which is essential when hosting multiple selectable lists in the same window.
Step 2: Render Items Using Selectable with SelectOnNav
For each item in your list, call ImGui::Selectable() with the ImGuiSelectableFlags_SelectOnNav flag. You can use either the pointer-based approach or the query-based approach:
Pointer-based modification:
for (int n = 0; n < items.Size; n++)
{
bool selected = selection[n];
if (ImGui::Selectable(items[n], &selected,
ImGuiSelectableFlags_SelectOnNav))
selection[n] = selected; // ImGui modified 'selected' if user clicked
}
Query-based toggle detection:
for (int n = 0; n < items.Size; n++)
{
ImGui::Selectable(items[n], false, ImGuiSelectableFlags_SelectOnNav);
if (ImGui::IsItemToggledSelection())
selection[n] = !selection[n]; // Toggle detected after rendering
}
The ImGuiSelectableFlags_SelectOnNav flag is mandatory for proper multi-select behavior, as it forwards navigation-based selection events to the ImGuiMultiSelectIO structure.
Step 3: Apply Selection Requests
After drawing all items, process accumulated selection requests from Shift-clicks and box-select operations:
ImGui::ApplyRequests(ms_io); // Processes range requests into ms_io->Selection
// Synchronize your external storage with ImGui's selection state
for (int n = 0; n < items.Size; n++)
selection[n] = ms_io->Selection[n];
The ApplyRequests function, implemented in imgui_widgets.cpp (lines 8700-8800), converts complex user interactions into simple boolean array updates.
Step 4: Finalize with EndMultiSelect
Close the multi-select block to finalize box-select state and cleanup:
ImGui::EndMultiSelect(ms_io);
While the ImGuiMultiSelectIO pointer's destructor automatically handles cleanup, explicit closure ensures proper scope management.
Essential Flags for Multi-Select Configuration
-
ImGuiMultiSelectFlags_SingleSelect: Restricts selection to one item, creating radio-list behavior while retaining the multi-select framework.
-
ImGuiMultiSelectFlags_ScopeWindow: Defines the selection scope as the entire window, allowing box-select to originate anywhere in the window and clearing selection when clicking outside items.
-
ImGuiMultiSelectFlags_ScopeRect: Limits box-select and clear-on-outside to the rectangle between Begin/End calls, isolating multiple lists within the same window.
-
ImGuiSelectableFlags_SelectOnNav: Required flag for
Selectable()that enables automatic selection when navigation focus enters an item, critical for keyboard and gamepad support. -
ImGuiSelectableFlags_AllowOverlap: Permits hit-testing of overlapping items, useful when list entries contain embedded buttons or controls.
Complete Working Examples
Basic Vector-Based Selection
This example demonstrates the standard pattern using ImVector<bool> for selection storage:
void ShowMultiSelectList()
{
static const char* items[] = { "Apple", "Banana", "Cherry", "Date", "Elderberry" };
static ImVector<bool> selection;
if (selection.empty())
selection.resize(IM_ARRAYSIZE(items), false);
// Step 1: Begin multi-select context
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
ImGuiMultiSelectFlags_ScopeWindow,
selection.Size,
IM_ARRAYSIZE(items));
// Step 2: Render selectable items
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
{
bool selected = selection[n];
if (ImGui::Selectable(items[n], &selected,
ImGuiSelectableFlags_SelectOnNav))
selection[n] = selected;
}
// Step 3: Apply requests and sync storage
ImGui::ApplyRequests(ms_io);
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
selection[n] = ms_io->Selection[n];
// Step 4: End context
ImGui::EndMultiSelect(ms_io);
}
Custom Storage Synchronization
For applications requiring custom data structures, implement a synchronization method:
struct SelectionStorage
{
ImVector<bool> Selected;
void SyncFrom(const ImGuiMultiSelectIO* ms_io)
{
for (int i = 0; i < Selected.Size; i++)
Selected[i] = ms_io->Selection[i];
}
};
void ShowCustomStorageList()
{
static const char* items[] = { "One", "Two", "Three", "Four" };
static SelectionStorage storage;
if (storage.Selected.empty())
storage.Selected.resize(IM_ARRAYSIZE(items), false);
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
ImGuiMultiSelectFlags_ScopeWindow,
storage.Selected.Size,
IM_ARRAYSIZE(items));
for (int i = 0; i < IM_ARRAYSIZE(items); ++i)
{
bool selected = storage.Selected[i];
ImGui::Selectable(items[i], &selected,
ImGuiSelectableFlags_SelectOnNav);
storage.Selected[i] = selected;
}
ImGui::ApplyRequests(ms_io);
storage.SyncFrom(ms_io); // Custom synchronization
ImGui::EndMultiSelect(ms_io);
}
Reference implementations demonstrating manual and API-driven approaches appear in imgui_demo.cpp (lines 2780-3060).
Summary
- Initialize context: Use
ImGui::BeginMultiSelect()with scope flags before rendering items. - Render with navigation: Always include
ImGuiSelectableFlags_SelectOnNavwhen callingImGui::Selectable(). - Process requests: Call
ImGui::ApplyRequests()after rendering to handle Shift-clicks and box-select. - Synchronize storage: Copy the
ms_io->Selectionarray to your external storage after applying requests. - Close properly: End blocks with
ImGui::EndMultiSelect()to finalize state. - Source references: Public API resides in
imgui.h(lines 51-66), implementation inimgui_widgets.cpp(lines 7395-7587), and demos inimgui_demo.cpp.
Frequently Asked Questions
What is the difference between ScopeWindow and ScopeRect?
ImGuiMultiSelectFlags_ScopeWindow allows box-select operations to start anywhere within the host window and clears selection when clicking on window background. ImGuiMultiSelectFlags_ScopeRect restricts these interactions to the rectangular area defined between BeginMultiSelect() and EndMultiSelect(), which prevents interference when displaying multiple selectable lists in the same window.
How do I detect selection toggles without using the bool pointer parameter?
Call ImGui::IsItemToggledSelection() immediately after ImGui::Selectable(). This function returns true if the item was toggled during that frame, allowing you to write if (ImGui::IsItemToggledSelection()) selection[i] = !selection[i]; instead of passing a pointer to Selectable().
Can I use multi-select with TreeNode instead of Selectable?
Yes, the multi-select API works with any item bounding box, including TreeNode() widgets. Ensure you set ImGuiTreeNodeFlags_OpenOnArrow and handle selection state appropriately, though Selectable() remains the primary widget for list-based multi-selection as demonstrated in imgui_demo.cpp.
How do I implement single-select behavior using this API?
Pass ImGuiMultiSelectFlags_SingleSelect to ImGui::BeginMultiSelect(). This flag enforces that only one item can be selected at a time, automatically clearing previous selections when a new item is chosen, effectively creating a radio-list behavior while maintaining the full navigation and interaction framework.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →