Browse Source

MultiSelect: Box-Select: Fixed holes when using with clipper (in 1D list.)

Clipper accounts for Selectable() layout oddity as BoxSelect is sensitive to it.
Also tweaked scroll triggering region inward.
Rename ImGuiMultiSelectFlags_NoBoxSelectScroll to ImGuiMultiSelectFlags_BoxSelectNoScroll.
Fixed use with ImGuiMultiSelectFlags_SinglaSelect.
ocornut 1 year ago
parent
commit
1ac469b50f
5 changed files with 60 additions and 25 deletions
  1. 16 2
      imgui.cpp
  2. 2 2
      imgui.h
  3. 12 5
      imgui_demo.cpp
  4. 2 2
      imgui_internal.h
  5. 28 14
      imgui_widgets.cpp

+ 16 - 2
imgui.cpp

@@ -3079,9 +3079,23 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
                 data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0));
                 data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0));
 
 
             // Add visible range
             // Add visible range
+            float min_y = window->ClipRect.Min.y;
+            float max_y = window->ClipRect.Max.y;
+
+            // Add box selection range
+            if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
+                if (ms->Storage->Window == window && ms->Storage->BoxSelectActive)
+                {
+                    // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos.
+                    // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that.
+                    // As a workaround we currently half ItemSpacing worth on each side.
+                    min_y -= g.Style.ItemSpacing.y;
+                    max_y += g.Style.ItemSpacing.y;
+                }
+
             const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0;
             const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0;
             const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0;
             const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0;
-            data->Ranges.push_back(ImGuiListClipperRange::FromPositions(window->ClipRect.Min.y, window->ClipRect.Max.y, off_min, off_max));
+            data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max));
         }
         }
 
 
         // Convert position ranges to item index ranges
         // Convert position ranges to item index ranges
@@ -13438,7 +13452,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
 
 
     IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()
     IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()
     g.DragDropTargetRect = bb;
     g.DragDropTargetRect = bb;
-    g.DragDropTargetClipRect = window->ClipRect; // May want to be overriden by user depending on use case?
+    g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case?
     g.DragDropTargetId = id;
     g.DragDropTargetId = id;
     g.DragDropWithinTarget = true;
     g.DragDropWithinTarget = true;
     return true;
     return true;

+ 2 - 2
imgui.h

@@ -2774,8 +2774,8 @@ enum ImGuiMultiSelectFlags_
     ImGuiMultiSelectFlags_None                  = 0,
     ImGuiMultiSelectFlags_None                  = 0,
     ImGuiMultiSelectFlags_SingleSelect          = 1 << 0,   // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho!
     ImGuiMultiSelectFlags_SingleSelect          = 1 << 0,   // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho!
     ImGuiMultiSelectFlags_NoSelectAll           = 1 << 1,   // Disable CTRL+A shortcut sending a SelectAll request.
     ImGuiMultiSelectFlags_NoSelectAll           = 1 << 1,   // Disable CTRL+A shortcut sending a SelectAll request.
-    ImGuiMultiSelectFlags_BoxSelect             = 1 << 2,   // Enable box-selection. Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.
-    ImGuiMultiSelectFlags_NoBoxSelectScroll     = 1 << 3,   // Disable scrolling when box-selecting near edges of scope.
+    ImGuiMultiSelectFlags_BoxSelect             = 1 << 2,   // Enable box-selection. Box-selection + clipper is currently only supported for 1D list (not with 2D grid). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.
+    ImGuiMultiSelectFlags_BoxSelectNoScroll     = 1 << 3,   // Disable scrolling when box-selecting near edges of scope.
     ImGuiMultiSelectFlags_ClearOnEscape         = 1 << 4,   // Clear selection when pressing Escape while scope is focused.
     ImGuiMultiSelectFlags_ClearOnEscape         = 1 << 4,   // Clear selection when pressing Escape while scope is focused.
     ImGuiMultiSelectFlags_ClearOnClickVoid      = 1 << 5,   // Clear selection when clicking on empty location within scope.
     ImGuiMultiSelectFlags_ClearOnClickVoid      = 1 << 5,   // Clear selection when clicking on empty location within scope.
     ImGuiMultiSelectFlags_ScopeWindow           = 1 << 6,   // Scope for _ClearOnClickVoid and _BoxSelect is whole window (Default). Use if BeginMultiSelect() covers a whole window.
     ImGuiMultiSelectFlags_ScopeWindow           = 1 << 6,   // Scope for _ClearOnClickVoid and _BoxSelect is whole window (Default). Use if BeginMultiSelect() covers a whole window.

+ 12 - 5
imgui_demo.cpp

@@ -3031,6 +3031,7 @@ static void ShowDemoWindowMultiSelect()
             ImGui::BulletText("Shift modifier for range selection.");
             ImGui::BulletText("Shift modifier for range selection.");
             ImGui::BulletText("CTRL+A to select all.");
             ImGui::BulletText("CTRL+A to select all.");
             ImGui::BulletText("Escape to clear selection.");
             ImGui::BulletText("Escape to clear selection.");
+            ImGui::BulletText("Click and drag to box-select.");
             ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.");
             ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.");
 
 
             // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection
             // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection
@@ -3041,7 +3042,7 @@ static void ShowDemoWindowMultiSelect()
             // The BeginListBox() has no actual purpose for selection logic (other that offering a scrolling region).
             // The BeginListBox() has no actual purpose for selection logic (other that offering a scrolling region).
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             {
             {
-                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
+                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect;
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 selection.ApplyRequests(ms_io, ITEMS_COUNT);
                 selection.ApplyRequests(ms_io, ITEMS_COUNT);
 
 
@@ -3076,7 +3077,7 @@ static void ShowDemoWindowMultiSelect()
             ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT);
             ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT);
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             {
             {
-                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
+                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect;
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 selection.ApplyRequests(ms_io, ITEMS_COUNT);
                 selection.ApplyRequests(ms_io, ITEMS_COUNT);
 
 
@@ -3142,7 +3143,7 @@ static void ShowDemoWindowMultiSelect()
 
 
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)))
             {
             {
-                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
+                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect;
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
                 selection.ApplyRequests(ms_io, items.Size);
                 selection.ApplyRequests(ms_io, items.Size);
 
 
@@ -3259,7 +3260,7 @@ static void ShowDemoWindowMultiSelect()
             static bool use_drag_drop = true;
             static bool use_drag_drop = true;
             static bool show_in_table = false;
             static bool show_in_table = false;
             static bool show_color_button = false;
             static bool show_color_button = false;
-            static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None;
+            static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect;
             static WidgetType widget_type = WidgetType_Selectable;
             static WidgetType widget_type = WidgetType_Selectable;
 
 
             if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; }
             if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; }
@@ -3273,7 +3274,7 @@ static void ShowDemoWindowMultiSelect()
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SingleSelect", &flags, ImGuiMultiSelectFlags_SingleSelect);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SingleSelect", &flags, ImGuiMultiSelectFlags_SingleSelect);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect", &flags, ImGuiMultiSelectFlags_BoxSelect);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect", &flags, ImGuiMultiSelectFlags_BoxSelect);
-            ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoBoxSelectScroll", &flags, ImGuiMultiSelectFlags_NoBoxSelectScroll);
+            ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelectNoScroll", &flags, ImGuiMultiSelectFlags_BoxSelectNoScroll);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);
             ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);
             if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))
             if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))
@@ -9621,6 +9622,7 @@ struct ExampleAssetsBrowser
     // Options
     // Options
     bool            ShowTypeOverlay = true;
     bool            ShowTypeOverlay = true;
     bool            AllowDragUnselected = false;
     bool            AllowDragUnselected = false;
+    bool            AllowBoxSelect = false;     // Unsupported for 2D selection for now.
     float           IconSize = 32.0f;
     float           IconSize = 32.0f;
     int             IconSpacing = 10;
     int             IconSpacing = 10;
     int             IconHitSpacing = 4;         // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
     int             IconHitSpacing = 4;         // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
@@ -9724,6 +9726,9 @@ struct ExampleAssetsBrowser
 
 
                 ImGui::SeparatorText("Selection Behavior");
                 ImGui::SeparatorText("Selection Behavior");
                 ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected);
                 ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected);
+                ImGui::BeginDisabled(); // Unsupported for 2D selection for now.
+                ImGui::Checkbox("Allow box-selection", &AllowBoxSelect);
+                ImGui::EndDisabled();
 
 
                 ImGui::SeparatorText("Layout");
                 ImGui::SeparatorText("Layout");
                 ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f");
                 ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f");
@@ -9772,6 +9777,8 @@ struct ExampleAssetsBrowser
             ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid;
             ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid;
             if (AllowDragUnselected)
             if (AllowDragUnselected)
                 ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; // To allow dragging an unselected item without altering selection.
                 ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; // To allow dragging an unselected item without altering selection.
+            if (AllowBoxSelect)
+                ms_flags |= ImGuiMultiSelectFlags_BoxSelect; // FIXME-MULTISELECT: Box-select not yet supported for 2D selection when using clipper.
             ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags);
             ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags);
 
 
             // Use custom selection adapter: store ID in selection (recommended)
             // Use custom selection adapter: store ID in selection (recommended)

+ 2 - 2
imgui_internal.h

@@ -1724,8 +1724,8 @@ struct IMGUI_API ImGuiMultiSelectTempData
     ImVec2                  ScopeRectMin;
     ImVec2                  ScopeRectMin;
     ImVec2                  BackupCursorMaxPos;
     ImVec2                  BackupCursorMaxPos;
     ImGuiID                 BoxSelectId;
     ImGuiID                 BoxSelectId;
-    ImRect                  BoxSelectRectCurr;  // Selection rectangle in absolute coordinates (derived from Storage->BoxSelectStartPosRel + MousePos)
     ImRect                  BoxSelectRectPrev;
     ImRect                  BoxSelectRectPrev;
+    ImRect                  BoxSelectRectCurr;  // Selection rectangle in absolute coordinates (derived every frame from Storage->BoxSelectStartPosRel + MousePos)
     ImGuiSelectionUserData  BoxSelectLastitem;
     ImGuiSelectionUserData  BoxSelectLastitem;
     ImGuiKeyChord           KeyMods;
     ImGuiKeyChord           KeyMods;
     bool                    LoopRequestClear;
     bool                    LoopRequestClear;
@@ -3102,7 +3102,7 @@ namespace ImGui
     inline ImRect           WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); }
     inline ImRect           WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); }
     inline ImRect           WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); }
     inline ImRect           WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); }
     inline ImVec2           WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p)  { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); }
     inline ImVec2           WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p)  { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); }
-    inline ImVec2           WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); }
+    inline ImVec2           WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p)  { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); }
 
 
     // Windows: Display Order and Focus Order
     // Windows: Display Order and Focus Order
     IMGUI_API void          FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0);
     IMGUI_API void          FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0);

+ 28 - 14
imgui_widgets.cpp

@@ -6749,6 +6749,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
     const ImVec2 text_max(min_x + size.x, pos.y + size.y);
     const ImVec2 text_max(min_x + size.x, pos.y + size.y);
 
 
     // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
     // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
+    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currenty doesn't allow offsetting CursorPos.
     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
     {
     {
@@ -7135,20 +7136,22 @@ static void BoxSelectStart(ImGuiMultiSelectState* storage, ImGuiSelectionUserDat
     storage->BoxSelectStartPosRel = storage->BoxSelectEndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
     storage->BoxSelectStartPosRel = storage->BoxSelectEndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
 }
 }
 
 
-static void BoxSelectScrollWithMouseDrag(ImGuiWindow* window, const ImRect& r)
+static void BoxSelectScrollWithMouseDrag(ImGuiWindow* window, const ImRect& inner_r)
 {
 {
     ImGuiContext& g = *GImGui;
     ImGuiContext& g = *GImGui;
-    for (int n = 0; n < 2; n++)
+    for (int n = 0; n < 2; n++) // each axis
     {
     {
-        float dist = (g.IO.MousePos[n] > r.Max[n]) ? g.IO.MousePos[n] - r.Max[n] : (g.IO.MousePos[n] < r.Min[n]) ? g.IO.MousePos[n] - r.Min[n] : 0.0f;
-        if (dist == 0.0f || (dist < 0.0f && window->Scroll[n] < 0.0f) || (dist > 0.0f && window->Scroll[n] >= window->ScrollMax[n]))
+        const float mouse_pos = g.IO.MousePos[n];
+        const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f;
+        const float scroll_curr = window->Scroll[n];
+        if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))
             continue;
             continue;
-        float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
-        float scroll_step = IM_ROUND(g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime);
+        const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
+        const float scroll_step = IM_ROUND(g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime);
         if (n == 0)
         if (n == 0)
-            ImGui::SetScrollX(window, window->Scroll[n] + scroll_step);
+            ImGui::SetScrollX(window, scroll_curr + scroll_step);
         else
         else
-            ImGui::SetScrollY(window, window->Scroll[n] + scroll_step);
+            ImGui::SetScrollY(window, scroll_curr + scroll_step);
     }
     }
 }
 }
 
 
@@ -7165,6 +7168,8 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
     g.CurrentMultiSelect = ms;
     g.CurrentMultiSelect = ms;
     if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
     if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
         flags |= ImGuiMultiSelectFlags_ScopeWindow;
         flags |= ImGuiMultiSelectFlags_ScopeWindow;
+    if (flags & ImGuiMultiSelectFlags_SingleSelect)
+        flags &= ~ImGuiMultiSelectFlags_BoxSelect;
 
 
     // FIXME: BeginFocusScope()
     // FIXME: BeginFocusScope()
     const ImGuiID id = window->IDStack.back();
     const ImGuiID id = window->IDStack.back();
@@ -7250,12 +7255,20 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
         }
         }
         if (storage->BoxSelectActive)
         if (storage->BoxSelectActive)
         {
         {
+            // Current frame absolute prev/current rectangles are used to toggle selection.
+            // They are derived from positions relative to scrolling space.
+            const ImRect scope_rect = window->InnerClipRect;
             ImVec2 start_pos_abs = WindowPosRelToAbs(window, storage->BoxSelectStartPosRel);
             ImVec2 start_pos_abs = WindowPosRelToAbs(window, storage->BoxSelectStartPosRel);
-            ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, storage->BoxSelectEndPosRel);
+            ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, storage->BoxSelectEndPosRel); // Clamped already
+            ImVec2 curr_end_pos_abs = g.IO.MousePos;
+            if (ms->Flags & ImGuiMultiSelectFlags_ScopeWindow)  // Box-select scrolling only happens with ScopeWindow
+                curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
             ms->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
             ms->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
             ms->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
             ms->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
-            ms->BoxSelectRectCurr.Min = ImMin(start_pos_abs, g.IO.MousePos);
-            ms->BoxSelectRectCurr.Max = ImMax(start_pos_abs, g.IO.MousePos);
+            ms->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
+            ms->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
+            //GetForegroundDrawList()->AddRect(ms->BoxSelectRectPrev.Min, ms->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
+            //GetForegroundDrawList()->AddRect(ms->BoxSelectRectCurr.Min, ms->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
         }
         }
     }
     }
 
 
@@ -7303,7 +7316,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
         if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && storage->BoxSelectActive)
         if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && storage->BoxSelectActive)
         {
         {
             // Box-select: render selection rectangle
             // Box-select: render selection rectangle
-            ms->Storage->BoxSelectEndPosRel = WindowPosAbsToRel(window, g.IO.MousePos);
+            ms->Storage->BoxSelectEndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
             ImRect box_select_r = ms->BoxSelectRectCurr;
             ImRect box_select_r = ms->BoxSelectRectCurr;
             box_select_r.ClipWith(scope_rect);
             box_select_r.ClipWith(scope_rect);
             window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
             window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
@@ -7311,8 +7324,9 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
 
 
             // Box-select: scroll
             // Box-select: scroll
             ImRect scroll_r = scope_rect;
             ImRect scroll_r = scope_rect;
-            scroll_r.Expand(g.Style.FramePadding);
-            if ((ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms->Flags & ImGuiMultiSelectFlags_NoBoxSelectScroll) == 0 && !scroll_r.Contains(g.IO.MousePos))
+            scroll_r.Expand(-g.FontSize);
+            //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
+            if ((ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms->Flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0 && !scroll_r.Contains(g.IO.MousePos))
                 BoxSelectScrollWithMouseDrag(window, scroll_r);
                 BoxSelectScrollWithMouseDrag(window, scroll_r);
         }
         }
     }
     }