MultiSelect: WIP range-select (#1861) (rebased six millions times)
diff --git a/imgui.cpp b/imgui.cpp
index 733a5df..b1e9903 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -1273,6 +1273,7 @@
     TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell
     ColorButtonPosition         = ImGuiDir_Right;   // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
     ButtonTextAlign             = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
+    SelectableSpacing           = ImVec2(0.0f,0.0f);// Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
     SelectableTextAlign         = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
     SeparatorTextBorderSize     = 3.0f;             // Thickkness of border in SeparatorText()
     SeparatorTextAlign          = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
@@ -1321,6 +1322,7 @@
     LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);
     TabRounding = ImTrunc(TabRounding * scale_factor);
     TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
+    SelectableSpacing = ImTrunc(SelectableSpacing * scale_factor);
     SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
     DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
     DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);
@@ -3257,6 +3259,7 @@
     { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)},    // ImGuiStyleVar_TableAngledHeadersAngle
     { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
     { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) },           // ImGuiStyleVar_ButtonTextAlign
+    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableSpacing) },         // ImGuiStyleVar_SelectableSpacing
     { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) },       // ImGuiStyleVar_SelectableTextAlign
     { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)},    // ImGuiStyleVar_SeparatorTextBorderSize
     { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) },        // ImGuiStyleVar_SeparatorTextAlign
@@ -3822,6 +3825,7 @@
     g.MenusIdSubmittedThisFrame.clear();
     g.InputTextState.ClearFreeMemory();
     g.InputTextDeactivatedState.ClearFreeMemory();
+    g.MultiSelectScopeWindow = NULL;
 
     g.SettingsWindows.clear();
     g.SettingsHandlers.clear();
diff --git a/imgui.h b/imgui.h
index 26965fd..80e6ca7 100644
--- a/imgui.h
+++ b/imgui.h
@@ -44,6 +44,7 @@
 // [SECTION] ImGuiIO
 // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)
 // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
+// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
 // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)
 // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)
 // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)
@@ -174,6 +175,7 @@
 struct ImGuiInputTextCallbackData;  // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use)
 struct ImGuiKeyData;                // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions.
 struct ImGuiListClipper;            // Helper to manually clip large list of items
+struct ImGuiMultiSelectData;        // State for a BeginMultiSelect() block
 struct ImGuiOnceUponAFrame;         // Helper for running a block of code not more than once a frame
 struct ImGuiPayload;                // User data payload for drag and drop operations
 struct ImGuiPlatformImeData;        // Platform IME data for io.PlatformSetImeDataFn() function.
@@ -227,6 +229,7 @@
 typedef int ImGuiItemFlags;         // -> enum ImGuiItemFlags_       // Flags: for PushItemFlag(), shared by all items
 typedef int ImGuiKeyChord;          // -> ImGuiKey | ImGuiMod_XXX    // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.
 typedef int ImGuiPopupFlags;        // -> enum ImGuiPopupFlags_      // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()
+typedef int ImGuiMultiSelectFlags;  // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect()
 typedef int ImGuiSelectableFlags;   // -> enum ImGuiSelectableFlags_ // Flags: for Selectable()
 typedef int ImGuiSliderFlags;       // -> enum ImGuiSliderFlags_     // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc.
 typedef int ImGuiTabBarFlags;       // -> enum ImGuiTabBarFlags_     // Flags: for BeginTabBar()
@@ -262,6 +265,10 @@
 typedef ImWchar16 ImWchar;
 #endif
 
+// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect()
+// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.)
+typedef ImS64 ImGuiSelectionUserData;
+
 // Callback and functions types
 typedef int     (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data);    // Callback function for ImGui::InputText()
 typedef void    (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data);              // Callback function for ImGui::SetNextWindowSizeConstraints()
@@ -661,6 +668,14 @@
     IMGUI_API bool          Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height
     IMGUI_API bool          Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0));      // "bool* p_selected" point to the selection state (read-write), as a convenient helper.
 
+    // Multi-selection system for Selectable() and TreeNode() functions.
+    // This enables standard multi-selection/range-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow items to be fully clipped (= not submitted at all) when not visible.
+    // Read comments near ImGuiMultiSelectData for details.
+    // When enabled, Selectable() and TreeNode() functions will return true when selection needs toggling.
+    IMGUI_API ImGuiMultiSelectData* BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected);
+    IMGUI_API ImGuiMultiSelectData* EndMultiSelect();
+    IMGUI_API void                  SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);
+
     // Widgets: List Boxes
     // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
     // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items.
@@ -893,6 +908,7 @@
     IMGUI_API bool          IsItemDeactivated();                                                // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that require continuous editing.
     IMGUI_API bool          IsItemDeactivatedAfterEdit();                                       // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that require continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item).
     IMGUI_API bool          IsItemToggledOpen();                                                // was the last item open state toggled? set by TreeNode().
+    IMGUI_API bool          IsItemToggledSelection();                                           // was the last item selection state toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly)
     IMGUI_API bool          IsAnyItemHovered();                                                 // is any item hovered?
     IMGUI_API bool          IsAnyItemActive();                                                  // is any item active?
     IMGUI_API bool          IsAnyItemFocused();                                                 // is any item focused?
@@ -1683,6 +1699,7 @@
     ImGuiStyleVar_TableAngledHeadersAngle,  // float     TableAngledHeadersAngle
     ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2  TableAngledHeadersTextAlign
     ImGuiStyleVar_ButtonTextAlign,          // ImVec2    ButtonTextAlign
+    ImGuiStyleVar_SelectableSpacing,        // ImVec2    SelectableSpacing
     ImGuiStyleVar_SelectableTextAlign,      // ImVec2    SelectableTextAlign
     ImGuiStyleVar_SeparatorTextBorderSize,  // float     SeparatorTextBorderSize
     ImGuiStyleVar_SeparatorTextAlign,       // ImVec2    SeparatorTextAlign
@@ -2127,6 +2144,7 @@
     ImVec2      TableAngledHeadersTextAlign;// Alignment of angled headers within the cell
     ImGuiDir    ColorButtonPosition;        // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
     ImVec2      ButtonTextAlign;            // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).
+    ImVec2      SelectableSpacing;          // Horizontal and vertical spacing between selectables (by default they are canceling out the effect of ItemSpacing).
     ImVec2      SelectableTextAlign;        // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
     float       SeparatorTextBorderSize;    // Thickkness of border in SeparatorText()
     ImVec2      SeparatorTextAlign;         // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
@@ -2703,6 +2721,70 @@
 };
 
 //-----------------------------------------------------------------------------
+// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectData)
+//-----------------------------------------------------------------------------
+
+// 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.
+enum ImGuiMultiSelectFlags_
+{
+    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
+};
+
+// Abstract:
+// - This system implements standard multi-selection idioms (CTRL+Click/Arrow, SHIFT+Click/Arrow, etc) in a way that allow items to be
+//   fully clipped (= not submitted at all) when not visible. Clipping is typically provided by ImGuiListClipper.
+//   Handling all of this in a single pass imgui is a little tricky, and this is why we provide those functionalities.
+//   Note however that if you don't need SHIFT+Click/Arrow range-select, you can handle a simpler form of multi-selection yourself,
+//   by reacting to click/presses on Selectable() items and checking keyboard modifiers.
+//   The complexity of this system here is mostly caused by the handling of range-select while optionally allowing to clip elements.
+// - 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) with near zero
+//   performance penalty, but requires a little more work on the code. If you only have a few hundreds elements in your possible selection set,
+//   you may as well not bother with clipping, as the cost should be negligible (as least on imgui side).
+//   If you are not sure, always start without clipping and you can work your way to the more optimized version afterwards.
+// - The void* Src/Dst value represent a selectable object. They are the values you pass to SetNextItemMultiSelectData().
+//   Storing an integer index is the easiest thing to do, as SetRange requests will give you two end points. But the code never assume that sortable integers are used.
+// - In the spirit of imgui design, your code own the selection data. So this is designed to handle all kind of selection data: instructive (store a bool inside each object),
+//   external array (store an array aside from your objects), set (store only selected items in a hash/map/set), using intervals (store indices in an interval tree), etc.
+// Usage flow:
+//   1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection status. As a default value for the initial frame or when,
+//      resetting your selection state: you may use the value for your first item or a "null" value that matches the type stored in your void*.
+//   2) Honor Clear/SelectAll requests by updating your selection data.  [Only required if you are using a clipper in step 4]
+//   3) Set RangeSrcPassedBy=true if the RangeSrc item is part of the items clipped before the first submitted/visible item.  [Only required if you are using a clipper in step 4]
+//      This is because for range-selection we need to know if we are currently "inside" or "outside" the range.
+//      If you are using integer indices everywhere, this is easy to compute:  if (clipper.DisplayStart > (int)data->RangeSrc) { data->RangeSrcPassedBy = true; }
+//   4) Submit your items with SetNextItemMultiSelectData() + Selectable()/TreeNode() calls.
+//      Call IsItemSelectionToggled() to query with the selection state has been toggled, in which you need the info immediately (before EndMultiSelect()) for your display.
+//      When cannot reliably return a "IsItemSelected()" value because we need to consider clipped (unprocessed) item, this is why we return a toggle event instead.
+//   5) Call EndMultiSelect(). Save the value of ->RangeSrc for the next frame (you may convert the value in a format that is safe for persistance)
+//   6) Honor Clear/SelectAll/SetRange requests by updating your selection data. Always process them in this order (as you will receive Clear+SetRange request simultaneously)
+// If you submit all items (no clipper), Step 2 and 3 and will be handled by Selectable() on a per-item basis.
+struct ImGuiMultiSelectData
+{
+    bool    RequestClear;           // Begin, End   // Request user to clear selection
+    bool    RequestSelectAll;       // Begin, End   // Request user to select all
+    bool    RequestSetRange;        // End          // Request user to set or clear selection in the [RangeSrc..RangeDst] range
+    bool    RangeSrcPassedBy;       // After Begin  // Need to be set by user is RangeSrc was part of the clipped set before submitting the visible items. Ignore if not clipping.
+    bool    RangeValue;             // End          // End: parameter from RequestSetRange request. True = Select Range, False = Unselect range.
+    void*   RangeSrc;               // Begin, End   // End: parameter from RequestSetRange request + you need to save this value so you can pass it again next frame. / Begin: this is the value you passed to BeginMultiSelect()
+    void*   RangeDst;               // End          // End: parameter from RequestSetRange request.
+    int     RangeDirection;         // End          // End: parameter from RequestSetRange request. +1 if RangeSrc came before RangeDst, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values.
+
+    ImGuiMultiSelectData()  { Clear(); }
+    void Clear()
+    {
+        RequestClear = RequestSelectAll = RequestSetRange = RangeSrcPassedBy = RangeValue = false;
+        RangeSrc = RangeDst = NULL;
+        RangeDirection = 0;
+    }
+};
+
+//-----------------------------------------------------------------------------
 // [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData)
 // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList.
 //-----------------------------------------------------------------------------
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index dd25345..8e73126 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -72,6 +72,7 @@
 // [SECTION] Demo Window / ShowDemoWindow()
 // - ShowDemoWindow()
 // - sub section: ShowDemoWindowWidgets()
+// - sub section: ShowDemoWindowMultiSelect()
 // - sub section: ShowDemoWindowLayout()
 // - sub section: ShowDemoWindowPopups()
 // - sub section: ShowDemoWindowTables()
@@ -214,6 +215,7 @@
 // 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 @@
 //-----------------------------------------------------------------------------
 // - ShowDemoWindow()
 // - ShowDemoWindowWidgets()
+// - ShowDemoWindowMultiSelect()
 // - ShowDemoWindowLayout()
 // - ShowDemoWindowPopups()
 // - ShowDemoWindowTables()
@@ -1371,37 +1374,6 @@
             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 @@
             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 @@
                     ImGui::PopID();
                 }
 
+            ImGui::PopStyleVar(2);
             if (winning_state)
                 ImGui::PopStyleVar();
+
             ImGui::TreePop();
         }
         IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
@@ -1509,6 +1492,8 @@
         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 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 @@
             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");
diff --git a/imgui_internal.h b/imgui_internal.h
index 5d8f233..63b8040 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -134,6 +134,7 @@
 struct ImGuiLastItemData;           // Status storage for last submitted items
 struct ImGuiLocEntry;               // A localization entry.
 struct ImGuiMenuColumns;            // Simple column measurement, currently used for MenuItem() only
+struct ImGuiMultiSelectState;       // Multi-selection state
 struct ImGuiNavItemData;            // Result of a gamepad/keyboard directional navigation move query result
 struct ImGuiMetricsConfig;          // Storage for ShowMetricsWindow() and DebugNodeXXX() functions
 struct ImGuiNextWindowData;         // Storage for SetNextWindow** functions
@@ -854,6 +855,7 @@
     // Controlled by widget code
     ImGuiItemFlags_Inputable                = 1 << 20, // false     // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature.
     ImGuiItemFlags_HasSelectionUserData     = 1 << 21, // false     // Set by SetNextItemSelectionUserData()
+    ImGuiItemFlags_IsMultiSelect            = 1 << 22, // false     // Set by SetNextItemSelectionUserData()
 
     ImGuiItemFlags_Default_                 = ImGuiItemFlags_AutoClosePopups,    // Please don't change, use PushItemFlag() instead.
 
@@ -1201,10 +1203,6 @@
     inline void ClearFlags()    { Flags = ImGuiNextWindowDataFlags_None; }
 };
 
-// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect()
-// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.)
-typedef ImS64 ImGuiSelectionUserData;
-
 enum ImGuiNextItemDataFlags_
 {
     ImGuiNextItemDataFlags_None         = 0,
@@ -1711,8 +1709,20 @@
 // We always assume that -1 is an invalid value (which works for indices and pointers)
 #define ImGuiSelectionUserData_Invalid        ((ImGuiSelectionUserData)-1)
 
+#define IMGUI_HAS_MULTI_SELECT 1
 #ifdef IMGUI_HAS_MULTI_SELECT
-// <this is filled in 'range_select' branch>
+
+struct IMGUI_API ImGuiMultiSelectState
+{
+    ImGuiMultiSelectData    In;                     // The In requests are set and returned by BeginMultiSelect()
+    ImGuiMultiSelectData    Out;                    // The Out requests are finalized and returned by EndMultiSelect()
+    bool                    InRangeDstPassedBy;     // (Internal) set by the the item that match NavJustMovedToId when InRequestRangeSetNav is set.
+    bool                    InRequestSetRangeNav;   // (Internal) set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation.
+
+    ImGuiMultiSelectState() { Clear(); }
+    void Clear() { In.Clear(); Out.Clear(); InRangeDstPassedBy = InRequestSetRangeNav = false; }
+};
+
 #endif // #ifdef IMGUI_HAS_MULTI_SELECT
 
 //-----------------------------------------------------------------------------
@@ -2107,6 +2117,12 @@
     ImVec2                  NavWindowingAccumDeltaPos;
     ImVec2                  NavWindowingAccumDeltaSize;
 
+    // Range-Select/Multi-Select
+    ImGuiID                 MultiSelectScopeId;
+    ImGuiWindow*            MultiSelectScopeWindow;
+    ImGuiMultiSelectFlags   MultiSelectFlags;
+    ImGuiMultiSelectState   MultiSelectState;
+
     // Render
     float                   DimBgRatio;                         // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list)
 
@@ -2370,6 +2386,10 @@
         NavWindowingToggleLayer = false;
         NavWindowingToggleKey = ImGuiKey_None;
 
+        MultiSelectScopeId = 0;
+        MultiSelectScopeWindow = NULL;
+        MultiSelectFlags = 0;
+
         DimBgRatio = 0.0f;
 
         DragDropActive = DragDropWithinSource = DragDropWithinTarget = false;
@@ -3144,7 +3164,6 @@
     IMGUI_API ImVec2        CalcItemSize(ImVec2 size, float default_w, float default_h);
     IMGUI_API float         CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
     IMGUI_API void          PushMultiItemsWidths(int components, float width_full);
-    IMGUI_API bool          IsItemToggledSelection();                                   // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly)
     IMGUI_API ImVec2        GetContentRegionMaxAbs();
     IMGUI_API void          ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess);
 
@@ -3325,6 +3344,10 @@
     IMGUI_API int           TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx);
     IMGUI_API int           TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data);
 
+    // Multi-Select/Range-Select API
+    IMGUI_API void          MultiSelectItemHeader(ImGuiID id, bool* p_selected);
+    IMGUI_API void          MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed);
+
     // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API)
     IMGUI_API void          SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect);
     IMGUI_API void          BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns().
@@ -3461,7 +3484,6 @@
     IMGUI_API bool          DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags);
     IMGUI_API bool          SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb);
     IMGUI_API bool          SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0);
-    IMGUI_API void          SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);
 
     // Widgets: Tree Nodes
     IMGUI_API bool          TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index a7ab721..a07908c 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -6465,6 +6465,26 @@
     bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
     const bool was_selected = selected;
 
+    // Multi-selection support (header)
+    const bool is_multi_select = (g.MultiSelectScopeWindow == window);
+    if (is_multi_select)
+    {
+        flags |= ImGuiTreeNodeFlags_OpenOnArrow;
+        MultiSelectItemHeader(id, &selected);
+        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
+
+        // To handle drag and drop of multiple items we need to avoid clearing selection on click.
+        // Enabling this test makes actions using CTRL+SHIFT delay their effect on the mouse release which is annoying, but it allows drag and drop of multiple items.
+        if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
+            button_flags |= ImGuiButtonFlags_PressedOnClick;
+        else
+            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
+    }
+    else
+    {
+        button_flags |= ImGuiButtonFlags_NoKeyModifiers;
+    }
+
     bool hovered, held;
     bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
     bool toggled = false;
@@ -6472,7 +6492,7 @@
     {
         if (pressed && g.DragDropHoldJustPressedId != id)
         {
-            if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
+            if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id && !is_multi_select))
                 toggled = true;
             if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
                 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
@@ -6507,13 +6527,23 @@
         }
     }
 
-    // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
-    if (selected != was_selected) //-V547
+    // Multi-selection support (footer)
+    if (is_multi_select)
+    {
+        bool pressed_copy = pressed && !toggled;
+        MultiSelectItemFooter(id, &selected, &pressed_copy);
+        if (pressed)
+            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
+    }
+
+    if (selected != was_selected)
         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
 
     // Render
     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
     ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact;
+    if (is_multi_select)
+        nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
     if (display_frame)
     {
         // Framed type
@@ -6727,8 +6757,8 @@
     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
     {
-        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
-        const float spacing_y = style.ItemSpacing.y;
+        const float spacing_x = span_all_columns ? 0.0f : ImMax(style.ItemSpacing.x - style.SelectableSpacing.x, 0.0f);
+        const float spacing_y = ImMax(style.ItemSpacing.y - style.SelectableSpacing.y, 0.0f);
         const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
         const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
         bb.Min.x -= spacing_L;
@@ -6783,20 +6813,43 @@
     if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
     if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
 
+    // Multi-selection support (header)
+    const bool is_multi_select = (g.MultiSelectScopeWindow == window);
     const bool was_selected = selected;
+    if (is_multi_select)
+    {
+        MultiSelectItemHeader(id, &selected);
+        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
+
+        // To handle drag and drop of multiple items we need to avoid clearing selection on click.
+        // Enabling this test makes actions using CTRL+SHIFT delay their effect on the mouse release which is annoying, but it allows drag and drop of multiple items.
+        if (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore))
+            button_flags |= ImGuiButtonFlags_PressedOnClick;
+        else
+            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
+    }
+
     bool hovered, held;
     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
 
-    // Auto-select when moved into
-    // - This will be more fully fleshed in the range-select branch
-    // - This is not exposed as it won't nicely work with some user side handling of shift/control
-    // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
-    //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
-    //   - (2) usage will fail with clipped items
-    //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
-    if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
-        if (g.NavJustMovedToId == id)
-            selected = pressed = true;
+    // Multi-selection support (footer)
+    if (is_multi_select)
+    {
+        MultiSelectItemFooter(id, &selected, &pressed);
+    }
+    else
+    {
+        // Auto-select when moved into
+        // - This will be more fully fleshed in the range-select branch
+        // - This is not exposed as it won't nicely work with some user side handling of shift/control
+        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
+        //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
+        //   - (2) usage will fail with clipped items
+        //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
+        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
+            if (g.NavJustMovedToId == id)
+                selected = pressed = true;
+    }
 
     // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
     if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
@@ -6810,18 +6863,27 @@
     if (pressed)
         MarkItemEdited(id);
 
-    // In this branch, Selectable() cannot toggle the selection so this will never trigger.
-    if (selected != was_selected) //-V547
+    if (selected != was_selected)
         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
 
     // Render
     if (hovered || selected)
     {
-        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
+        // FIXME-MULTISELECT, FIXME-STYLE: Color for 'selected' elements? ImGuiCol_HeaderSelected
+        ImU32 col;
+        if (selected && !hovered)
+            col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f));
+        else
+            col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
         RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
     }
     if (g.NavId == id)
-        RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding);
+    {
+        ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding;
+        if (is_multi_select)
+            nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
+        RenderNavHighlight(bb, id, nav_highlight_flags);
+    }
 
     if (span_all_columns)
     {
@@ -6841,7 +6903,7 @@
         EndDisabled();
 
     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
-    return pressed; //-V1020
+    return pressed || (was_selected != selected); //-V1020
 }
 
 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
@@ -7049,16 +7111,227 @@
 //-------------------------------------------------------------------------
 // [SECTION] Widgets: Multi-Select support
 //-------------------------------------------------------------------------
+// - BeginMultiSelect()
+// - EndMultiSelect()
+// - SetNextItemMultiSelectData()
+// - MultiSelectItemHeader() [Internal]
+// - MultiSelectItemFooter() [Internal]
+//-------------------------------------------------------------------------
+
+ImGuiMultiSelectData* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, void* range_ref, bool range_ref_is_selected)
+{
+    ImGuiContext& g = *ImGui::GetCurrentContext();
+    ImGuiWindow* window = g.CurrentWindow;
+
+    IM_ASSERT(g.MultiSelectScopeId == 0);    // No recursion allowed yet (we could allow it if we deem it useful)
+    IM_ASSERT(g.MultiSelectFlags == 0);
+
+    ImGuiMultiSelectState* ms = &g.MultiSelectState;
+    g.MultiSelectScopeId = window->IDStack.back();
+    g.MultiSelectScopeWindow = window;
+    g.MultiSelectFlags = flags;
+    ms->Clear();
+
+    if ((flags & ImGuiMultiSelectFlags_NoMultiSelect) == 0)
+    {
+        ms->In.RangeSrc = ms->Out.RangeSrc = range_ref;
+        ms->In.RangeValue = ms->Out.RangeValue = range_ref_is_selected;
+    }
+
+    // Auto clear when using Navigation to move within the selection (we compare SelectScopeId so it possible to use multiple lists inside a same window)
+    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.MultiSelectScopeId)
+    {
+        if (g.IO.KeyShift)
+            ms->InRequestSetRangeNav = true;
+        if (!g.IO.KeyCtrl && !g.IO.KeyShift)
+            ms->In.RequestClear = true;
+    }
+
+    // Select All helper shortcut
+    if (!(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
+        if (IsWindowFocused() && g.IO.KeyCtrl && IsKeyPressed(GetKeyIndex(ImGuiKey_A)))
+            ms->In.RequestSelectAll = true;
+
+#ifdef IMGUI_DEBUG_MULTISELECT
+    if (ms->In.RequestClear)     printf("[%05d] BeginMultiSelect: RequestClear\n", g.FrameCount);
+    if (ms->In.RequestSelectAll) printf("[%05d] BeginMultiSelect: RequestSelectAll\n", g.FrameCount);
+#endif
+
+    return &ms->In;
+}
+
+ImGuiMultiSelectData* ImGui::EndMultiSelect()
+{
+    ImGuiContext& g = *ImGui::GetCurrentContext();
+    ImGuiMultiSelectState* ms = &g.MultiSelectState;
+    IM_ASSERT(g.MultiSelectScopeId != 0);
+    if (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)
+        ms->Out.RangeValue = true;
+    g.MultiSelectScopeId = 0;
+    g.MultiSelectScopeWindow = NULL;
+    g.MultiSelectFlags = 0;
+
+#ifdef IMGUI_DEBUG_MULTISELECT
+    if (ms->Out.RequestClear)     printf("[%05d] EndMultiSelect: RequestClear\n", g.FrameCount);
+    if (ms->Out.RequestSelectAll) printf("[%05d] EndMultiSelect: RequestSelectAll\n", g.FrameCount);
+    if (ms->Out.RequestSetRange)  printf("[%05d] EndMultiSelect: RequestSetRange %p..%p = %d\n", g.FrameCount, ms->Out.RangeSrc, ms->Out.RangeDst, ms->Out.RangeValue);
+#endif
+
+    return &ms->Out;
+}
 
 void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
 {
     // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
     // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
     ImGuiContext& g = *GImGui;
-    g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
+    if (g.MultiSelectScopeId != 0)
+        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
+    else
+        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
     g.NextItemData.SelectionUserData = selection_user_data;
 }
 
+void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiMultiSelectState* ms = &g.MultiSelectState;
+
+    IM_ASSERT((g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid) && "Forgot to call SetNextItemMultiSelectData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
+    void* item_data = (void*)g.NextItemData.SelectionUserData;
+
+    // Apply Clear/SelectAll requests requested by BeginMultiSelect().
+    // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
+    // If you are using a clipper (aka not submitting every element of the list) you need to process the Clear/SelectAll request after calling BeginMultiSelect()
+    bool selected = *p_selected;
+    if (ms->In.RequestClear)
+        selected = false;
+    else if (ms->In.RequestSelectAll)
+        selected = true;
+
+    const bool is_range_src = (ms->In.RangeSrc == item_data);
+    if (is_range_src)
+        ms->In.RangeSrcPassedBy = true;
+
+    // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection)
+    // For this to work, IF the user is clipping items, they need to set RangeSrcPassedBy = true to notify the system.
+    if (ms->InRequestSetRangeNav)
+    {
+        IM_ASSERT(id != 0);
+        IM_ASSERT(g.IO.KeyShift);
+        const bool is_range_dst = !ms->InRangeDstPassedBy && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.
+        if (is_range_dst)
+            ms->InRangeDstPassedBy = true;
+        if (is_range_src || is_range_dst || ms->In.RangeSrcPassedBy != ms->InRangeDstPassedBy)
+            selected = ms->In.RangeValue;
+        else if (!g.IO.KeyCtrl)
+            selected = false;
+    }
+
+    *p_selected = selected;
+}
+
+void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiMultiSelectState* ms = &g.MultiSelectState;
+
+    void* item_data = (void*)g.NextItemData.SelectionUserData;
+
+    bool selected = *p_selected;
+    bool pressed = *p_pressed;
+    bool is_ctrl = g.IO.KeyCtrl;
+    bool is_shift = g.IO.KeyShift;
+    const bool is_multiselect = (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoMultiSelect) == 0;
+
+    // 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)
+            pressed = true;
+    }
+
+    // Right-click handling: this could be moved at the Selectable() level.
+    bool hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
+    if (hovered && IsMouseClicked(1))
+    {
+        SetFocusID(g.LastItemData.ID, window);
+        if (!pressed && !selected)
+        {
+            pressed = true;
+            is_ctrl = is_shift = false;
+        }
+    }
+
+    if (pressed)
+    {
+        //-------------------------------------------------------------------------------------------------------------------------------------------------
+        // ACTION                           | Begin     | Item Old        | Item New                                                   | End
+        //-------------------------------------------------------------------------------------------------------------------------------------------------
+        // Keys Navigated, Ctrl=0, Shift=0  | In.Clear  | Clear -> Sel=0  | Src=item, Pressed -> Sel=1                                 |
+        // Keys Navigated, Ctrl=0, Shift=1  | n/a       | n/a             | Dst=item, Pressed -> Sel=1,   Out.Clear, Out.SetRange=1    | Clear + SetRange
+        // Keys Navigated, Ctrl=1, Shift=1  | n/a       | n/a             | Dst=item, Pressed -> Sel=Src, Out.Clear, Out.SetRange=Src  | Clear + SetRange
+        // Mouse Pressed, Ctrl=0, Shift=0   | n/a       | n/a (Sel=1)     | Src=item, Pressed -> Sel=1,   Out.Clear, Out.SetRange=1    | Clear + SetRange
+        // Mouse Pressed, Ctrl=0, Shift=1   | n/a       | n/a             | Dst=item, Pressed -> Sel=1,   Out.Clear, Out.SetRange=1    | Clear + SetRange
+        //-------------------------------------------------------------------------------------------------------------------------------------------------
+
+        ImGuiInputSource input_source = (g.NavJustMovedToId != 0 && g.NavWindow == window && g.NavJustMovedToId == g.LastItemData.ID) ? g.NavInputSource : ImGuiInputSource_Mouse;
+        if (is_shift && is_multiselect)
+        {
+            ms->Out.RequestSetRange = true;
+            ms->Out.RangeDst = item_data;
+            if (!is_ctrl)
+                ms->Out.RangeValue = true;
+            ms->Out.RangeDirection = ms->In.RangeSrcPassedBy ? +1 : -1;
+        }
+        else
+        {
+            selected = (!is_ctrl || (g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect)) ? true : !selected;
+            ms->Out.RangeSrc = ms->Out.RangeDst = item_data;
+            ms->Out.RangeValue = selected;
+        }
+
+        if (input_source == ImGuiInputSource_Mouse)
+        {
+            // Mouse click without CTRL clears the selection, unless the clicked item is already selected
+            bool preserve_existing_selection = g.DragDropActive;
+            if (is_multiselect && !is_ctrl && !preserve_existing_selection)
+                ms->Out.RequestClear = true;
+            if (is_multiselect && !is_shift && !preserve_existing_selection && ms->Out.RequestClear)
+            {
+                // For toggle selection unless there is a Clear request, we can handle it completely locally without sending a RangeSet request.
+                IM_ASSERT(ms->Out.RangeSrc == ms->Out.RangeDst); // Setup by block above
+                ms->Out.RequestSetRange = true;
+                ms->Out.RangeValue = selected;
+                ms->Out.RangeDirection = +1;
+            }
+            if (!is_multiselect)
+            {
+                // Clear selection, set single item range
+                IM_ASSERT(ms->Out.RangeSrc == item_data && ms->Out.RangeDst == item_data); // Setup by block above
+                ms->Out.RequestClear = true;
+                ms->Out.RequestSetRange = true;
+            }
+        }
+        else if (input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad)
+        {
+            if (!is_multiselect)
+                ms->Out.RequestClear = true;
+            else if (is_shift && !is_ctrl && is_multiselect)
+                ms->Out.RequestClear = true;
+        }
+    }
+
+    // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)
+    if (ms->Out.RangeSrc == item_data && is_ctrl && is_shift && is_multiselect && !(g.MultiSelectFlags & ImGuiMultiSelectFlags_NoUnselect))
+        ms->Out.RangeValue = selected;
+
+    *p_selected = selected;
+    *p_pressed = pressed;
+}
 
 //-------------------------------------------------------------------------
 // [SECTION] Widgets: ListBox