MultiSelect: Box-Select: Further refactor to extra mode code away from multi-select function into box-select funcitons.

This commit is contained in:
ocornut 2023-12-20 22:34:50 +01:00
parent 907268a430
commit f3d77d8e71
3 changed files with 109 additions and 80 deletions

View File

@ -14997,8 +14997,8 @@ void ImGui::ShowMetricsWindow(bool* p_open)
// Details for MultiSelect // Details for MultiSelect
if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount())) if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount()))
{ {
ImGuiBoxSelectState* ms = &g.BoxSelectState; ImGuiBoxSelectState* bs = &g.BoxSelectState;
Text("BoxSelect ID=0x%08X, Starting = %d, Active %d", ms->ID, ms->IsStarting, ms->IsActive); BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs->ID, bs->IsStarting, bs->IsActive);
for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++) for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++)
if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n)) if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n))
DebugNodeMultiSelectState(state); DebugNodeMultiSelectState(state);

View File

@ -1718,6 +1718,7 @@ struct ImGuiBoxSelectState
bool IsActive; bool IsActive;
bool IsStarting; bool IsStarting;
bool IsStartedFromVoid; // Starting click was not from an item. bool IsStartedFromVoid; // Starting click was not from an item.
bool RequestClear;
ImGuiKeyChord KeyMods : 16; // Latched key-mods for box-select logic. ImGuiKeyChord KeyMods : 16; // Latched key-mods for box-select logic.
ImVec2 StartPosRel; // Start position in window-contents relative space (to support scrolling) ImVec2 StartPosRel; // Start position in window-contents relative space (to support scrolling)
ImVec2 EndPosRel; // End position in window-contents relative space ImVec2 EndPosRel; // End position in window-contents relative space
@ -1729,7 +1730,6 @@ struct ImGuiBoxSelectState
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 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;
ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges.
ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); }
}; };
@ -1762,6 +1762,7 @@ struct IMGUI_API ImGuiMultiSelectTempData
bool NavIdPassedBy; bool NavIdPassedBy;
bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem.
bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set.
ImGuiSelectionUserData BoxSelectLastitem; // Copy of last submitted item data, used to merge output ranges.
ImGuiMultiSelectTempData() { Clear(); } ImGuiMultiSelectTempData() { Clear(); }
void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation.
@ -3403,6 +3404,10 @@ namespace ImGui
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 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); IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data);
// Box-Select API
IMGUI_API bool BeginBoxSelect(ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags);
IMGUI_API void EndBoxSelect(const ImRect& scope_rect, bool enable_scroll);
// Multi-Select API // Multi-Select API
IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags);
IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed);

View File

@ -7113,12 +7113,15 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// [SECTION] Widgets: Box-Select support // [SECTION] Widgets: Box-Select support
// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// - BoxSelectStart() [Internal] // - BoxSelectStartDrag() [Internal]
// - BoxSelectScrollWithMouseDrag() [Internal] // - BoxSelectScrollWithMouseDrag() [Internal]
// - BeginBoxSelect() [Internal]
// - EndBoxSelect() [Internal]
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
static void BoxSelectStart(ImGuiID id, ImGuiSelectionUserData clicked_item) static void BoxSelectStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiBoxSelectState* bs = &g.BoxSelectState; ImGuiBoxSelectState* bs = &g.BoxSelectState;
@ -7159,6 +7162,91 @@ static void BoxSelectScrollWithMouseDrag(ImGuiWindow* window, const ImRect& inne
} }
} }
bool ImGui::BeginBoxSelect(ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
{
ImGuiContext& g = *GImGui;
ImGuiBoxSelectState* bs = &g.BoxSelectState;
KeepAliveID(box_select_id);
if (bs->ID != box_select_id)
return false;
bs->UnclipMode = false;
bs->RequestClear = false;
// IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
if (bs->IsStarting && IsMouseDragPastThreshold(0))
{
bs->IsActive = true;
bs->Window = window;
bs->IsStarting = false;
SetActiveID(bs->ID, window);
if (bs->IsStartedFromVoid && (bs->KeyMods & ImGuiMod_Shift) == 0)
bs->RequestClear = true;
}
else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
{
bs->IsActive = bs->IsStarting = false;
if (g.ActiveId == bs->ID)
ClearActiveID();
bs->ID = 0;
}
if (!bs->IsActive)
return false;
// 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, bs->StartPosRel);
ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // 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);
bs->BoxSelectRectPrev.Min = ImMin(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.Max = ImMax(start_pos_abs, curr_end_pos_abs);
// Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
// Storing an extra rect used by widgets supporting box-select.
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
{
bs->UnclipRect = bs->BoxSelectRectPrev;
bs->UnclipRect.Add(bs->BoxSelectRectCurr);
bs->UnclipMode = true;
}
//GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.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);
return true;
}
void ImGui::EndBoxSelect(const ImRect& scope_rect, bool enable_scroll)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiBoxSelectState* bs = &g.BoxSelectState;
IM_ASSERT(bs->IsActive);
bs->UnclipMode = false;
// Render selection rectangle
bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
ImRect box_select_r = bs->BoxSelectRectCurr;
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->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling
// Scroll
if (enable_scroll)
{
ImRect scroll_r = scope_rect;
scroll_r.Expand(-g.FontSize);
//GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
if (!scroll_r.Contains(g.IO.MousePos))
BoxSelectScrollWithMouseDrag(window, scroll_r);
}
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// [SECTION] Widgets: Multi-Select support // [SECTION] Widgets: Multi-Select support
@ -7268,59 +7356,9 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags)
if (flags & ImGuiMultiSelectFlags_BoxSelect) if (flags & ImGuiMultiSelectFlags_BoxSelect)
{ {
ms->BoxSelectId = GetID("##BoxSelect"); ms->BoxSelectId = GetID("##BoxSelect");
KeepAliveID(ms->BoxSelectId); ms->BoxSelectLastitem = ImGuiSelectionUserData_Invalid;
} if (BeginBoxSelect(window, ms->BoxSelectId, flags))
if ((flags & ImGuiMultiSelectFlags_BoxSelect) && ms->BoxSelectId == bs->ID) request_clear |= bs->RequestClear;
{
bs->UnclipMode = false;
// BoxSelectStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
if (bs->IsStarting && IsMouseDragPastThreshold(0))
{
bs->IsActive = true;
bs->Window = ms->Storage->Window;
bs->IsStarting = false;
SetActiveID(ms->BoxSelectId, window);
if (bs->IsStartedFromVoid && (bs->KeyMods & ImGuiMod_Shift) == 0)
request_clear = true;
}
else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
{
bs->ID = 0;
bs->IsActive = bs->IsStarting = false;
if (g.ActiveId == ms->BoxSelectId)
ClearActiveID();
}
if (bs->IsActive)
{
// 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, bs->StartPosRel);
ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // 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);
bs->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
bs->BoxSelectRectPrev.Min = ImMin(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.Max = ImMax(start_pos_abs, curr_end_pos_abs);
// Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
// Storing an extra rect used by widgets supporting box-select.
if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
{
bs->UnclipRect = bs->BoxSelectRectPrev;
bs->UnclipRect.Add(bs->BoxSelectRectCurr);
bs->UnclipMode = true;
}
//GetForegroundDrawList()->AddRect(ms->BoxSelectNoClipRect.Min, ms->BoxSelectNoClipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
//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);
}
} }
if (request_clear || request_select_all) if (request_clear || request_select_all)
@ -7364,24 +7402,10 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
storage->NavIdSelected = -1; storage->NavIdSelected = -1;
} }
ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId); if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && GetBoxSelectState(ms->BoxSelectId))
if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && bs != NULL)
{ {
// Box-select: render selection rectangle bool enable_scroll = (ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms->Flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view EndBoxSelect(scope_rect, enable_scroll);
ImRect box_select_r = bs->BoxSelectRectCurr;
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->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling
// Box-select: scroll
ImRect scroll_r = scope_rect;
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);
bs->UnclipMode = false;
} }
} }
@ -7395,7 +7419,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
{ {
if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect) if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect)
if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1) if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
BoxSelectStart(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); BoxSelectStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
@ -7544,7 +7568,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
selected = pressed = true; selected = pressed = true;
} }
// Box-select handling // Box-select toggle handling
if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
{ {
const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
@ -7554,12 +7578,12 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
selected = !selected; selected = !selected;
ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, item_data, item_data }; ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, item_data, item_data };
ImGuiSelectionRequest* prev_req = (ms->IO.Requests.Size > 0) ? &ms->IO.Requests.Data[ms->IO.Requests.Size - 1] : NULL; ImGuiSelectionRequest* prev_req = (ms->IO.Requests.Size > 0) ? &ms->IO.Requests.Data[ms->IO.Requests.Size - 1] : NULL;
if (prev_req && prev_req->Type == ImGuiSelectionRequestType_SetRange && prev_req->RangeLastItem == bs->LastSubmittedItem && prev_req->RangeSelected == selected) if (prev_req && prev_req->Type == ImGuiSelectionRequestType_SetRange && prev_req->RangeLastItem == ms->BoxSelectLastitem && prev_req->RangeSelected == selected)
prev_req->RangeLastItem = item_data; // Merge span into same request prev_req->RangeLastItem = item_data; // Merge span into same request
else else
ms->IO.Requests.push_back(req); ms->IO.Requests.push_back(req);
} }
bs->LastSubmittedItem = item_data; ms->BoxSelectLastitem = item_data;
} }
// Right-click handling: this could be moved at the Selectable() level. // Right-click handling: this could be moved at the Selectable() level.
@ -7588,7 +7612,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect) if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect)
if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
BoxSelectStart(ms->BoxSelectId, item_data); BoxSelectStartDrag(ms->BoxSelectId, item_data);
//---------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------
// ACTION | Begin | Pressed/Activated | End // ACTION | Begin | Pressed/Activated | End