From dee39f8da88e57e54daebd9eebf8de751b74add3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 12 Mar 2025 14:15:51 +0100 Subject: [PATCH] Scrollbar: stabilize visibility of ScrollbarX when detecting a feedback loop. (#8488, #3285) --- imgui.cpp | 8 ++++++++ imgui_internal.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index ae81cc64ac..a020a7eb2b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7456,9 +7456,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + bool scrollbar_x_prev = window->ScrollbarX; //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + + // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) + window->ScrollbarXToggledHistoryMask = (window->ScrollbarXToggledHistoryMask << 1) | ((scrollbar_x_prev != window->ScrollbarX) ? 0x01 : 0x00); + const int scrollbar_x_toggled_in_past_frames = (window->ScrollbarXToggledHistoryMask != 0) ? ImCountSetBits(window->ScrollbarXToggledHistoryMask) : 0; + if (scrollbar_x_toggled_in_past_frames >= 4 && scrollbar_x_toggled_in_past_frames < (1 << (sizeof(window->ScrollbarXToggledHistoryMask) * 8)) - 1) + window->ScrollbarX = true; + if (window->ScrollbarX && !window->ScrollbarY) window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index aaa2027df9..b7c110ada3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -370,6 +370,7 @@ IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +static inline int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } // Helpers: String #define ImStrlen strlen @@ -2514,6 +2515,7 @@ struct IMGUI_API ImGuiWindow ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar. bool ScrollbarX, ScrollbarY; // Are scrollbars visible? + ImU8 ScrollbarXToggledHistoryMask; // Used to stabilize scrollbar visibility in case of feedback loops bool Active; // Set to true on Begin(), unless Collapsed bool WasActive; bool WriteAccessed; // Set to true when any widget access the current window -- 2.45.2.windows.1