overlay: fix Options tab contents jitter (#751)

## 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
This commit is contained in:
drmext
2026-06-11 21:03:33 +00:00
committed by GitHub
parent 267add3227
commit 18d6d9783a
+23 -7
View File
@@ -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);
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();
}