Multi-Select: Box-Select: improve dirty unclip rectangle calculation + use in ImGuiMultiSelectFlags_BoxSelect1d mode when needed (e.g. wheel scrolling up). (#7994, #8250, #7821, #7850, #7970)
This commit is contained in:
@@ -61,6 +61,10 @@ Other Changes:
|
|||||||
- Multi-Select:
|
- Multi-Select:
|
||||||
- Fixed an issue using Multi-Select within a Table causing column width measurement to
|
- Fixed an issue using Multi-Select within a Table causing column width measurement to
|
||||||
be invalid when trailing column contents is not submitted in the last row. (#9341, #8250)
|
be invalid when trailing column contents is not submitted in the last row. (#9341, #8250)
|
||||||
|
- Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect1d mode while scrolling.
|
||||||
|
Notably, using mouse wheel while holding a box-selection could lead items close to windows
|
||||||
|
edges from not being correctly unselected. (#7994, #8250, #7821, #7850, #7970)
|
||||||
|
- Box-Select: improved dirty/unclip rectangle logic for ImGuiMultiSelectFlags_BoxSelect2d.
|
||||||
- Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where
|
- Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where
|
||||||
items out of view wouldn't be properly selected while scrolling while mouse cursor
|
items out of view wouldn't be properly selected while scrolling while mouse cursor
|
||||||
is hovering outside of selection scope. (#7994, #1861, #6518)
|
is hovering outside of selection scope. (#7994, #1861, #6518)
|
||||||
|
|||||||
@@ -606,6 +606,8 @@ struct IMGUI_API ImRect
|
|||||||
bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; }
|
bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; }
|
||||||
void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; }
|
void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; }
|
||||||
void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }
|
void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }
|
||||||
|
void AddX(float x) { if (Min.x > x) Min.x = x; if (Max.x < x) Max.x = x; }
|
||||||
|
void AddY(float y) { if (Min.y > y) Min.y = y; if (Max.y < y) Max.y = y; }
|
||||||
void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; }
|
void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; }
|
||||||
void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }
|
void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }
|
||||||
void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }
|
void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }
|
||||||
@@ -1893,6 +1895,7 @@ struct ImGuiBoxSelectState
|
|||||||
// Temporary/Transient data
|
// Temporary/Transient data
|
||||||
bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select.
|
bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select.
|
||||||
ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets.
|
ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets.
|
||||||
|
ImRect UnclipRects[2]; // Per-axis versions.
|
||||||
ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)
|
ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)
|
||||||
ImRect BoxSelectRectCurr;
|
ImRect BoxSelectRectCurr;
|
||||||
|
|
||||||
|
|||||||
@@ -1330,6 +1330,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
|
|||||||
// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields.
|
// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields.
|
||||||
void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect)
|
void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect)
|
||||||
{
|
{
|
||||||
|
if (rect.IsInverted())
|
||||||
|
return;
|
||||||
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
|
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
|
||||||
{
|
{
|
||||||
ImGuiTableColumn* column = &table->Columns[column_n];
|
ImGuiTableColumn* column = &table->Columns[column_n];
|
||||||
|
|||||||
+55
-14
@@ -7508,8 +7508,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
|||||||
RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb);
|
RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb);
|
||||||
|
|
||||||
#ifdef IMGUI_DEBUG_BOXSELECT
|
#ifdef IMGUI_DEBUG_BOXSELECT
|
||||||
if (g.BoxSelectState.UnclipMode)
|
if (g.BoxSelectState.UnclipMode) { GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label, label_end); }
|
||||||
GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Automatically close popups
|
// Automatically close popups
|
||||||
@@ -7827,7 +7826,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Current frame absolute prev/current rectangles are used to toggle selection.
|
// Current frame absolute prev/current rectangles are used to toggle selection.
|
||||||
// They are derived from positions relative to scrolling space.
|
// They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates.
|
||||||
ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
|
ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
|
||||||
ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
|
ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
|
||||||
ImVec2 curr_end_pos_abs = g.IO.MousePos;
|
ImVec2 curr_end_pos_abs = g.IO.MousePos;
|
||||||
@@ -7837,26 +7836,68 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI
|
|||||||
bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
|
bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
|
||||||
bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
|
bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
|
||||||
bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
|
bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
|
||||||
|
//IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs->StartPosRel.x, bs->StartPosRel.y, bs->EndPosRel.x, bs->EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y);
|
||||||
|
|
||||||
// Box-select 2D mode detects change of the rectangle.
|
// Box-select 2D mode detects change of the rectangle.
|
||||||
// Storing unclip rect used by widgets supporting box-select.
|
// Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them).
|
||||||
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
|
// To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry.
|
||||||
|
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||||
{
|
{
|
||||||
if (bs->BoxSelectRectPrev.Min != bs->BoxSelectRectCurr.Min || bs->BoxSelectRectPrev.Max != bs->BoxSelectRectCurr.Max)
|
// For both sides, compute the area differing between Prev and Curr rectangles.
|
||||||
bs->UnclipMode = true;
|
bs->UnclipRects[0] = bs->UnclipRects[1] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||||
|
for (int side = 0; side < 2; side++)
|
||||||
|
{
|
||||||
|
ImVec2 d_min = (side == 0) ? ImMin(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMin(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
|
||||||
|
ImVec2 d_max = (side == 0) ? ImMax(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMax(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
|
||||||
|
if (d_min.x != d_max.x)
|
||||||
|
{
|
||||||
|
bs->UnclipRects[0].AddX(d_min.x);
|
||||||
|
bs->UnclipRects[0].AddX(d_max.x);
|
||||||
|
}
|
||||||
|
if (d_min.y != d_max.y)
|
||||||
|
{
|
||||||
|
bs->UnclipRects[1].AddY(d_min.y);
|
||||||
|
bs->UnclipRects[1].AddY(d_max.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Always update rect even if we don't use it.
|
ImRect box_select_intersection = bs->BoxSelectRectPrev;
|
||||||
bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect X coordinates could be intersection of Prev and Curr rect on X axis.
|
box_select_intersection.Add(bs->BoxSelectRectCurr);
|
||||||
bs->UnclipRect.Add(bs->BoxSelectRectCurr);
|
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
|
||||||
|
if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
|
||||||
|
{
|
||||||
|
bs->UnclipRects[0].AddY(box_select_intersection.Min.y);
|
||||||
|
bs->UnclipRects[0].AddY(box_select_intersection.Max.y);
|
||||||
|
}
|
||||||
|
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||||
|
if (bs->BoxSelectRectPrev.Min.y != bs->BoxSelectRectCurr.Min.y || bs->BoxSelectRectPrev.Max.y != bs->BoxSelectRectCurr.Max.y)
|
||||||
|
{
|
||||||
|
bs->UnclipRects[1].AddX(box_select_intersection.Min.x);
|
||||||
|
bs->UnclipRects[1].AddX(box_select_intersection.Max.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge both rectangles into one.
|
||||||
|
// FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should
|
||||||
|
// ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation.
|
||||||
|
bs->UnclipRect = bs->UnclipRects[0];
|
||||||
|
bs->UnclipRect.Add(bs->UnclipRects[1]);
|
||||||
|
if (!bs->UnclipRect.IsInverted() && (!window->ClipRect.Contains(bs->UnclipRect.Min) || !window->ClipRect.Contains(bs->UnclipRect.Max))) // !! Don't use Contains(ImRect)
|
||||||
|
bs->UnclipMode = true;
|
||||||
|
if (bs->UnclipMode && g.CurrentTable != NULL)
|
||||||
|
TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); // No need submitting both
|
||||||
}
|
}
|
||||||
if (bs->UnclipMode && g.CurrentTable != NULL)
|
|
||||||
TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect);
|
|
||||||
|
|
||||||
#ifdef IMGUI_DEBUG_BOXSELECT
|
#ifdef IMGUI_DEBUG_BOXSELECT
|
||||||
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
|
//GetForegroundDrawList()->AddRect(scope_rect.Min, scope_rect.Max, IM_COL32(0, 255, 0, 200), 0.0f, 0, 4.0f);
|
||||||
GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255,255,0,200) : IM_COL32(255,0,0,200), 0.0f, 0, 4.0f);
|
|
||||||
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
|
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
|
||||||
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
|
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
|
||||||
|
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||||
|
{
|
||||||
|
for (ImRect& unclip_r : bs->UnclipRects)
|
||||||
|
if (!unclip_r.IsInverted())
|
||||||
|
GetForegroundDrawList()->AddRect(unclip_r.Min, unclip_r.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 4.0f);
|
||||||
|
GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 2.0f);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user