From 15d26c976d86967f9d39aec5cb8c7987757f6ae4 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 9 Sep 2024 21:57:08 +1000 Subject: [PATCH] ImGuiFullscreen: Allow smooth scrolling --- dep/imgui/imgui.vcxproj | 2 +- dep/imgui/imgui.vcxproj.filters | 2 +- dep/imgui/include/imgui.h | 3 ++ dep/imgui/include/imgui_internal.h | 3 +- dep/imgui/src/imgui.cpp | 52 ++++++++++++++++++++++++++---- dep/imgui/src/imgui_widgets.cpp | 4 +-- src/core/fullscreen_ui.cpp | 10 ++++++ src/util/imgui_fullscreen.cpp | 10 +++++- src/util/imgui_fullscreen.h | 1 + 9 files changed, 74 insertions(+), 13 deletions(-) diff --git a/dep/imgui/imgui.vcxproj b/dep/imgui/imgui.vcxproj index e3a9cd928..dbb7c8ed7 100644 --- a/dep/imgui/imgui.vcxproj +++ b/dep/imgui/imgui.vcxproj @@ -5,8 +5,8 @@ + - diff --git a/dep/imgui/imgui.vcxproj.filters b/dep/imgui/imgui.vcxproj.filters index d0fa61434..49e6bc224 100644 --- a/dep/imgui/imgui.vcxproj.filters +++ b/dep/imgui/imgui.vcxproj.filters @@ -3,12 +3,12 @@ - + diff --git a/dep/imgui/include/imgui.h b/dep/imgui/include/imgui.h index 05e48a175..ac290e57d 100644 --- a/dep/imgui/include/imgui.h +++ b/dep/imgui/include/imgui.h @@ -1717,6 +1717,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding + ImGuiStyleVar_ScrollSmooth, // float ScrollSmooth ImGuiStyleVar_COUNT }; @@ -2180,6 +2181,8 @@ struct ImGuiStyle ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + float ScrollSmooth; // Smooth scrolling amount: 1.0f no smoothing. Anything above 1.0f will make the scroll delta more smooth. + IMGUI_API ImGuiStyle(); IMGUI_API void ScaleAllSizes(float scale_factor); }; diff --git a/dep/imgui/include/imgui_internal.h b/dep/imgui/include/imgui_internal.h index ab43922ba..b1e5b808b 100644 --- a/dep/imgui/include/imgui_internal.h +++ b/dep/imgui/include/imgui_internal.h @@ -2618,7 +2618,8 @@ struct IMGUI_API ImGuiWindow ImGuiID MoveId; // == window->GetID("#MOVE") ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) - ImVec2 Scroll; + ImVec2 Scroll; // Current Visible Scroll position + ImVec2 ScrollExpected; // Current Expected Scroll position ImVec2 ScrollMax; ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered diff --git a/dep/imgui/src/imgui.cpp b/dep/imgui/src/imgui.cpp index b26edc46e..5c2182432 100644 --- a/dep/imgui/src/imgui.cpp +++ b/dep/imgui/src/imgui.cpp @@ -1303,6 +1303,7 @@ ImGuiStyle::ImGuiStyle() HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + ScrollSmooth = 1.0f; // Disabled by default. It's just immediate jump from ScrollExpected to the visual Scroll. // Default theme ImGui::StyleColorsDark(this); @@ -3297,6 +3298,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollSmooth) }, // ImGuiStyleVar_ScrollSmooth }; const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) @@ -3926,6 +3928,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL IDStack.push_back(ID); MoveId = GetID("#MOVE"); ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollExpected = ImVec2(0, 0); ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); AutoFitFramesX = AutoFitFramesY = -1; AutoPosLastDirection = ImGuiDir_None; @@ -9524,7 +9527,7 @@ void ImGui::UpdateMouseWheel() LockWheelingWindow(window, wheel.x); float max_step = window->InnerRect.GetWidth() * 0.67f; float scroll_step = ImTrunc(ImMin(2 * window->CalcFontSize(), max_step)); - SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); + SetScrollX(window, window->ScrollExpected.x - wheel.x * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } if (do_scroll[ImGuiAxis_Y]) @@ -9532,7 +9535,7 @@ void ImGui::UpdateMouseWheel() LockWheelingWindow(window, wheel.y); float max_step = window->InnerRect.GetHeight() * 0.67f; float scroll_step = ImTrunc(ImMin(5 * window->CalcFontSize(), max_step)); - SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); + SetScrollY(window, window->ScrollExpected.y - wheel.y * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } } @@ -10761,11 +10764,46 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) float snap_max = window->ScrollMax[axis] + window->SizeFull[axis] - decoration_size[axis]; scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max, window->ScrollTargetEdgeSnapDist[axis], center_ratio); } - scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); + window->ScrollExpected[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f)); + + // Based on https://github.com/ocornut/imgui/pull/7348 + // TODO: Make it not dependent on frame rate, easiest way would be to multiply by delta_time / (1/60). + // Smooth scroll. + // Instead use "Scroll" value in the window, all setters that sets the scroll absolutely now points to + // "ScrollExpected" Here, we take from ScrollTarget (from some functions like ScrollHere + mouse wheel) to set + // the ScrollExpected value Also, Scroll var in window is processed to meet ScrollExpected Value + // + // The formula is pretty simple to generate a smooth scrolling that can be tweaked just by one float value. + // + // The Float is "ImGuiStyleVar_ScrollSmooth". Can be set on the style or via PushStyleVar. + // A Value of 1.0f is just inmediate (transported from ScrollExpected to Scroll). + // A Value higher of 1.0f will make the scrolling smoother. + // + // The ScrollExpected is also clamped (as previously the "Scroll" value) from 0 to sScrollMax + // + // The approach is frame bounded and not time bounded. + // It should be prefereable use a time bounded approach but this is pretty simple so we don't need to add extra + // vars to save a scrolling "start" time to have a delta / deal with posible increments during the scrolling + // itself (restar timer) Anyway it should not be complicated to add but this approach is small, simple, can be + // user or not and works pretty well + // + window->ScrollExpected[axis] = IM_ROUND(ImMax(window->ScrollExpected[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) - scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); + window->ScrollExpected[axis] = ImMin(window->ScrollExpected[axis], window->ScrollMax[axis]); + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + if (scroll[axis] != window->ScrollExpected[axis]) + { + const float multiplier = GImGui->IO.DeltaTime / (1.0f / 60.0f); + const float diff = window->ScrollExpected[axis] - scroll[axis]; + if (diff > 0) + scroll[axis] += ImMin(diff, (diff / (style.ScrollSmooth * multiplier))); + else + scroll[axis] -= ImMin(-diff, (-diff / (style.ScrollSmooth * multiplier))); + + scroll[axis] = window->Appearing ? window->ScrollExpected[axis] : scroll[axis]; + } } return scroll; } @@ -10858,13 +10896,13 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui float ImGui::GetScrollX() { ImGuiWindow* window = GImGui->CurrentWindow; - return window->Scroll.x; + return window->ScrollExpected.x; } float ImGui::GetScrollY() { ImGuiWindow* window = GImGui->CurrentWindow; - return window->Scroll.y; + return window->ScrollExpected.y; } float ImGui::GetScrollMaxX() diff --git a/dep/imgui/src/imgui_widgets.cpp b/dep/imgui/src/imgui_widgets.cpp index bd806ad08..8e52da89e 100644 --- a/dep/imgui/src/imgui_widgets.cpp +++ b/dep/imgui/src/imgui_widgets.cpp @@ -933,9 +933,9 @@ void ImGui::Scrollbar(ImGuiAxis axis) } float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; - ImS64 scroll = (ImS64)window->Scroll[axis]; + ImS64 scroll = (ImS64)window->ScrollExpected[axis]; ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners); - window->Scroll[axis] = (float)scroll; + window->ScrollExpected[axis] = (float)scroll; } // Vertical/Horizontal scrollbar diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index c8fb802ca..9e5efc492 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -571,6 +571,7 @@ bool FullscreenUI::Initialize() return false; ImGuiFullscreen::SetTheme(Host::GetBaseBoolSettingValue("Main", "UseLightFullscreenUITheme", false)); + ImGuiFullscreen::SetSmoothScrolling(Host::GetBaseBoolSettingValue("Main", "FullscreenUISmoothScrolling", true)); ImGuiFullscreen::UpdateLayoutScale(); if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("images/placeholder.png") || @@ -3114,6 +3115,13 @@ void FullscreenUI::DrawInterfaceSettingsPage() ImGuiFullscreen::SetTheme(bsi->GetBoolValue("Main", "UseLightFullscreenUITheme", false)); } + if (DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST, "Smooth Scrolling"), + FSUI_CSTR("Enables smooth scrolling of menus in Big Picture UI."), "Main", + "FullscreenUISmoothScrolling", true)) + { + ImGuiFullscreen::SetSmoothScrolling(bsi->GetBoolValue("Main", "FullscreenUISmoothScrolling", false)); + } + { // Have to do this the annoying way, because it's host-derived. const auto language_list = Host::GetAvailableLanguageList(); @@ -7392,6 +7400,7 @@ TRANSLATE_NOOP("FullscreenUI", "Enables alignment and bus exceptions. Not needed TRANSLATE_NOOP("FullscreenUI", "Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles."); TRANSLATE_NOOP("FullscreenUI", "Enables an additional three controller slots on each port. Not supported in all games."); TRANSLATE_NOOP("FullscreenUI", "Enables more precise frame pacing at the cost of battery life."); +TRANSLATE_NOOP("FullscreenUI", "Enables smooth scrolling of menus in Big Picture UI."); TRANSLATE_NOOP("FullscreenUI", "Enables the older, less accurate MDEC decoding routines. May be required for old replacement backgrounds to match/load."); TRANSLATE_NOOP("FullscreenUI", "Enables the replacement of background textures in supported games."); TRANSLATE_NOOP("FullscreenUI", "Encore Mode"); @@ -7716,6 +7725,7 @@ TRANSLATE_NOOP("FullscreenUI", "Simulates the system ahead of time and rolls bac TRANSLATE_NOOP("FullscreenUI", "Skip Duplicate Frame Display"); TRANSLATE_NOOP("FullscreenUI", "Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."); TRANSLATE_NOOP("FullscreenUI", "Slow Boot"); +TRANSLATE_NOOP("FullscreenUI", "Smooth Scrolling"); TRANSLATE_NOOP("FullscreenUI", "Smooths out blockyness between colour transitions in 24-bit content, usually FMVs."); TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 2D objects."); TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 3D objects."); diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 0bc4cab8a..3e6b10a3b 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -41,6 +41,7 @@ namespace ImGuiFullscreen { using MessageDialogCallbackVariant = std::variant; static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f; +static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f; static std::optional LoadTextureImage(std::string_view path); static std::shared_ptr UploadTexture(std::string_view path, const RGBA8Image& image); @@ -90,6 +91,7 @@ static u32 s_menu_button_index = 0; static u32 s_close_button_state = 0; static FocusResetType s_focus_reset_queued = FocusResetType::None; static bool s_light_theme = false; +static bool s_smooth_scrolling = false; static LRUCache> s_texture_cache(128, true); static std::shared_ptr s_placeholder_texture; @@ -275,6 +277,11 @@ void ImGuiFullscreen::Shutdown() s_message_dialog_callback = {}; } +void ImGuiFullscreen::SetSmoothScrolling(bool enabled) +{ + s_smooth_scrolling = enabled; +} + const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() { return s_placeholder_texture; @@ -763,6 +770,7 @@ bool ImGuiFullscreen::BeginFullscreenWindow(const ImVec2& position, const ImVec2 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(padding)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(rounding)); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollSmooth, s_smooth_scrolling ? SMOOTH_SCROLLING_SPEED : 1.0f); return ImGui::Begin(name, nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | @@ -772,7 +780,7 @@ bool ImGuiFullscreen::BeginFullscreenWindow(const ImVec2& position, const ImVec2 void ImGuiFullscreen::EndFullscreenWindow() { ImGui::End(); - ImGui::PopStyleVar(3); + ImGui::PopStyleVar(4); ImGui::PopStyleColor(); } diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index a1bd4117b..04907bbc6 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -120,6 +120,7 @@ ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size); bool Initialize(const char* placeholder_image_path); void SetTheme(bool light); +void SetSmoothScrolling(bool enabled); void SetFonts(ImFont* medium_font, ImFont* large_font); bool UpdateLayoutScale();