From 18d6d9783a903e314ab8161057be9ccbdb8538a3 Mon Sep 17 00:00:00 2001 From: drmext <71258889+drmext@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:03:33 +0000 Subject: [PATCH] overlay: fix Options tab contents jitter (#751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Link to GitHub Issue or related Pull Request, if one exists #747 ## Description of change The old approach called ImGui’s `SetScrollHereY()` while the options list was still being laid out. That caused one-frame scroll corrections and edge-snap jitter near scroll boundaries. The new approach in `build_options_tab()`: 1. **On click** - set a flag (`options_scroll_pending` for categories, `options_scroll_top` for group headers). 2. **During layout** - when building the target category, record its content position with `GetCursorPosY()` (don’t scroll yet). 3. **After all options are built** - apply scroll once by setting `window->Scroll.y` directly, and only if the position actually needs to change (0.5px threshold). That deferred, single-pass scroll avoids the shake while still supporting scroll-up, scroll-down, and re-click-to-recenter. ## Testing In spicecfg.exe and in-game overlay, Options tab: Open groups with many categories Scroll content down manually, then click a higher category in the left nav - content scrolls up to that section Re-click the same category after manual scroll - jumps back without shake Rapid re-clicks when already aligned - no jitter Switch group headers - still scrolls to top; re-click at top - no shake --- src/spice2x/overlay/windows/config.cpp | 32 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index b8eb143..ab716a7 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -406,19 +406,18 @@ namespace overlay::windows { // content: only the options belonging to the selected group. ImGui::BeginChild("OptionsContent", ImVec2(0, content_height), false); - if (this->options_scroll_top) { - ImGui::SetScrollY(0.0f); - this->options_scroll_top = false; - } + + const bool scroll_to_top = this->options_scroll_top; + this->options_scroll_top = false; + float scroll_to_category_y = -1.0f; // breathing room at the top of the content area ImGui::Dummy(ImVec2(0.0f, overlay::apply_scaling(4))); - // when a category was clicked in the nav, scroll the content to its section - auto scroll_anchor = [this](const std::string &category) { + // when a category was clicked in the nav, record its Y for deferred scroll + auto scroll_anchor = [this, &scroll_to_category_y](const std::string &category) { if (this->options_scroll_pending && this->options_category_selected == category) { - ImGui::Dummy(ImVec2(0.0f, 0.0f)); - ImGui::SetScrollHereY(0.0f); + scroll_to_category_y = ImGui::GetCursorPosY(); this->options_scroll_pending = false; } }; @@ -474,6 +473,23 @@ namespace overlay::windows { } } + // apply scroll after content layout to avoid mid-layout jitter + ImGuiWindow *content_window = ImGui::GetCurrentWindow(); + if (scroll_to_top) { + if (content_window->Scroll.y > 0.0f) { + content_window->Scroll.y = 0.0f; + } + content_window->ScrollTarget.y = FLT_MAX; + } else if (scroll_to_category_y >= 0.0f) { + const float spacing_y = ImMax( + content_window->WindowPadding.y, ImGui::GetStyle().ItemSpacing.y); + const float desired_scroll = ImMax(0.0f, scroll_to_category_y - spacing_y); + if (fabsf(content_window->Scroll.y - desired_scroll) > 0.5f) { + content_window->Scroll.y = desired_scroll; + } + content_window->ScrollTarget.y = FLT_MAX; + } + ImGui::EndChild(); }