From bce4db00bcccef072cd5a596b2f123395561e869 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 22 Nov 2023 19:30:08 +0100 Subject: [PATCH] Drag and Drop: Fixed drop target highlight on items temporarily pushing a widened clip rect. (#7049, #4281, #3272) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 10 ++++++---- imgui_internal.h | 8 ++++++-- imgui_widgets.cpp | 28 ++++++++++++++++++++-------- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a7e61bc3b..9561fc951 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -44,6 +44,9 @@ Breaking changes: Other changes: +- Drag and Drop: Fixed drop target highlight on items temporarily pushing a widened clip rect + (namely Selectables and Treenodes using SpanAllColumn flag) so the highlight properly covers + all columns. (#7049, #4281, #3272) - Misc: Fixed text functions fast-path for handling "%s" and "%.*s" to handle null pointers gracefully, like most printf implementations. (#7016, #3466, #6846) [@codefrog2002] - Misc: Renamed some defines in imstb_textedit.h to avoid conflicts when using unity/jumbo builds diff --git a/imgui.cpp b/imgui.cpp index 0cbaa09fd..a29f3372d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12778,13 +12778,14 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) IM_ASSERT(g.DragDropWithinTarget == false); g.DragDropTargetRect = bb; + g.DragDropTargetClipRect = window->ClipRect; // May want to be overriden by user depending on use case? g.DragDropTargetId = id; g.DragDropWithinTarget = true; return true; } // We don't use BeginDragDropTargetCustom() and duplicate its code because: -// 1) we use LastItemRectHoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 1) we use LastItemData's ImGuiItemStatusFlags_HoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) bool ImGui::BeginDragDropTarget() @@ -12812,6 +12813,7 @@ bool ImGui::BeginDragDropTarget() IM_ASSERT(g.DragDropWithinTarget == false); g.DragDropTargetRect = display_rect; + g.DragDropTargetClipRect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect; g.DragDropTargetId = id; g.DragDropWithinTarget = true; return true; @@ -12849,7 +12851,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop payload.Preview = was_accepted_previously; flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - RenderDragDropTargetRect(r); + RenderDragDropTargetRect(r, g.DragDropTargetClipRect); g.DragDropAcceptFrameCount = g.FrameCount; payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() @@ -12861,12 +12863,12 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop } // FIXME-STYLE FIXME-DRAGDROP: Settle on a proper default visuals for drop target. -void ImGui::RenderDragDropTargetRect(const ImRect& bb) +void ImGui::RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImRect bb_display = bb; - bb_display.ClipWith(window->ClipRect); // Clip THEN expand so we have a way to visualize that target is not entirely visible. + bb_display.ClipWith(item_clip_rect); // Clip THEN expand so we have a way to visualize that target is not entirely visible. bb_display.Expand(3.5f); bool push_clip_rect = !window->ClipRect.Contains(bb_display); if (push_clip_rect) diff --git a/imgui_internal.h b/imgui_internal.h index c39a68c37..9a3a82d48 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -837,6 +837,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HoveredWindow = 1 << 7, // Override the HoveredWindow test to allow cross-window hover testing. ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Set when the Focusable item just got focused by Tabbing (FIXME: to be removed soon) ImGuiItemStatusFlags_Visible = 1 << 9, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). + ImGuiItemStatusFlags_HasClipRect = 1 << 10, // g.LastItemData.ClipRect is valid // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -1221,7 +1222,9 @@ struct ImGuiLastItemData ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) - ImRect DisplayRect; // Display rectangle (only if ImGuiItemStatusFlags_HasDisplayRect is set) + // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags is set. + ImRect DisplayRect; // Display rectangle (ONLY VALID IF ImGuiItemStatusFlags_HasDisplayRect is set) + ImRect ClipRect; // Clip rectangle at the time of submitting item (ONLY VALID IF ImGuiItemStatusFlags_HasClipRect is set) ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } }; @@ -2038,6 +2041,7 @@ struct ImGuiContext int DragDropMouseButton; ImGuiPayload DragDropPayload; ImRect DragDropTargetRect; // Store rectangle of current target candidate (we favor small targets when overlapping) + ImRect DragDropTargetClipRect; // Store ClipRect at the time of item's drawing ImGuiID DragDropTargetId; ImGuiDragDropFlags DragDropAcceptFlags; float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the smaller surface) @@ -3168,7 +3172,7 @@ namespace ImGui IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); - IMGUI_API void RenderDragDropTargetRect(const ImRect& bb); + IMGUI_API void RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect); // Typing-Select API IMGUI_API ImGuiTypingSelectRequest* GetTypingSelectRequest(ImGuiTypingSelectFlags flags = ImGuiTypingSelectFlags_None); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6225583eb..45f0c2e58 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6232,7 +6232,11 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l } if (span_all_columns) + { TablePushBackgroundChannel(); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; + g.LastItemData.ClipRect = window->ClipRect; + } ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) @@ -6562,10 +6566,15 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, // which would be advantageous since most selectable are not selected. - if (span_all_columns && window->DC.CurrentColumns) - PushColumnsBackground(); - else if (span_all_columns && g.CurrentTable) - TablePushBackgroundChannel(); + if (span_all_columns) + { + if (g.CurrentTable) + TablePushBackgroundChannel(); + else if (window->DC.CurrentColumns) + PushColumnsBackground(); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; + g.LastItemData.ClipRect = window->ClipRect; + } // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; @@ -6616,10 +6625,13 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (g.NavId == id) RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); - if (span_all_columns && window->DC.CurrentColumns) - PopColumnsBackground(); - else if (span_all_columns && g.CurrentTable) - TablePopBackgroundChannel(); + if (span_all_columns) + { + if (g.CurrentTable) + TablePopBackgroundChannel(); + else if (window->DC.CurrentColumns) + PopColumnsBackground(); + } RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);