From 6bd5f75330e8a2973348dd08f6f21757e95216d8 Mon Sep 17 00:00:00 2001 From: Matthias Moninger <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 17 May 2023 22:19:44 +0300 Subject: [PATCH] Update the UI at screen refresh rate (#20214) * Update the UI at screen refresh rate * Decouple input from ticks, fix scroll at high frame rates * Fix holding down mouse button on buttons causing too many events * Subtract the initial delay to keep the same behavior as before * Guard against the rare case where the value might be 0 * Fix right click not working correctly * Fix odd behavior when using right click to scroll lists * Make touch work again, fix mouse panning in fullscreen (borderless) * Update changelog.txt --- distribution/changelog.txt | 1 + src/openrct2-ui/input/MouseInput.cpp | 47 ++++++++++++++++++---------- src/openrct2/Context.cpp | 12 +++---- src/openrct2/GameState.cpp | 2 -- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 2e50c623f6..e90371d755 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -3,6 +3,7 @@ - Feature: [OpenMusic#41] Official Title Theme by Allister Brimble. - Improved: [#20200] Allow audio files to play to up to 44100hz sample rate (from 22050hz). - Change: [#20110] Fix a few RCT1 build height parity discrepancies. +- Fix: [#6152] Camera and UI are no longer locked at 40hz providing a smoother experience. - Fix: [#19823] Parkobj, disallow overriding objects of different object types. - Fix: [#20111] All coaster types can access the new diagonal slope pieces. - Fix: [#20155] Fairground organ style 2 shows up as regular song, rather than for the merry-go-round. diff --git a/src/openrct2-ui/input/MouseInput.cpp b/src/openrct2-ui/input/MouseInput.cpp index 7fbfc8a902..bd97d9569e 100644 --- a/src/openrct2-ui/input/MouseInput.cpp +++ b/src/openrct2-ui/input/MouseInput.cpp @@ -32,6 +32,7 @@ #include #include #include +#include struct RCTMouseData { @@ -44,7 +45,7 @@ static RCTMouseData _mouseInputQueue[64]; static uint8_t _mouseInputQueueReadIndex = 0; static uint8_t _mouseInputQueueWriteIndex = 0; -static uint32_t _ticksSinceDragStart; +static std::optional _ticksSinceDragStart; static WidgetRef _dragWidget; static uint8_t _dragScrollIndex; static int32_t _originalWindowWidth; @@ -59,7 +60,7 @@ uint16_t gTooltipTimeout; WidgetRef gTooltipWidget; ScreenCoordsXY gTooltipCursor; -static int16_t _clickRepeatTicks; +static std::optional _clickRepeatTicks; static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords); static void InputWidgetOver(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex); @@ -178,7 +179,7 @@ static void InputScrollDragBegin(const ScreenCoordsXY& screenCoords, WindowBase* _dragWidget.window_classification = w->classification; _dragWidget.window_number = w->number; _dragWidget.widget_index = widgetIndex; - _ticksSinceDragStart = 0; + _ticksSinceDragStart = gCurrentRealTimeTicks; _dragScrollIndex = WindowGetScrollDataIndex(*w, widgetIndex); ContextHideCursor(); @@ -197,6 +198,8 @@ static void InputScrollDragContinue(const ScreenCoordsXY& screenCoords, WindowBa auto& scroll = w->scrolls[scrollIndex]; ScreenCoordsXY differentialCoords = screenCoords - gInputDragLast; + if (differentialCoords.x == 0 && differentialCoords.y == 0) + return; if (scroll.flags & HSCROLLBAR_VISIBLE) { @@ -242,10 +245,9 @@ static void InputScrollRight(const ScreenCoordsXY& screenCoords, MouseState stat switch (state) { case MouseState::Released: - _ticksSinceDragStart += gCurrentDeltaTime; if (screenCoords.x != 0 || screenCoords.y != 0) { - _ticksSinceDragStart = 1000; + _ticksSinceDragStart = std::nullopt; InputScrollDragContinue(screenCoords, w); } break; @@ -348,7 +350,7 @@ static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState else if (state == MouseState::RightRelease) { InputViewportDragEnd(); - if (_ticksSinceDragStart < 500) + if (_ticksSinceDragStart.has_value() && gCurrentRealTimeTicks - _ticksSinceDragStart.value() < 500) { // If the user pressed the right mouse button for less than 500 ticks, interpret as right click ViewportInteractionRightClick(screenCoords); @@ -525,7 +527,7 @@ static void InputViewportDragBegin(WindowBase& w) _inputState = InputState::ViewportRight; _dragWidget.window_classification = w.classification; _dragWidget.window_number = w.number; - _ticksSinceDragStart = 0; + _ticksSinceDragStart = gCurrentRealTimeTicks; auto cursorPosition = ContextGetCursorPosition(); gInputDragLast = cursorPosition; if (!gConfigGeneral.InvertViewportDrag) @@ -543,9 +545,11 @@ static void InputViewportDragContinue() Viewport* viewport; auto newDragCoords = ContextGetCursorPosition(); - const CursorState* cursorState = ContextGetCursorState(); auto differentialCoords = newDragCoords - gInputDragLast; + if (differentialCoords.x == 0 && differentialCoords.y == 0) + return; + w = WindowFindByNumber(_dragWidget.window_classification, _dragWidget.window_number); // #3294: Window can be closed during a drag session, so just finish @@ -557,7 +561,6 @@ static void InputViewportDragContinue() } viewport = w->viewport; - _ticksSinceDragStart += gCurrentDeltaTime; if (viewport == nullptr) { ContextShowCursor(); @@ -571,7 +574,7 @@ static void InputViewportDragContinue() // If the drag time is less than 500 the "drag" is usually interpreted as a right click. // As the user moved the mouse, don't interpret it as right click in any case. - _ticksSinceDragStart = 1000; + _ticksSinceDragStart = std::nullopt; differentialCoords.x = (viewport->zoom + 1).ApplyTo(differentialCoords.x); differentialCoords.y = (viewport->zoom + 1).ApplyTo(differentialCoords.y); @@ -586,6 +589,7 @@ static void InputViewportDragContinue() } } + const CursorState* cursorState = ContextGetCursorState(); if (cursorState->touch || gConfigGeneral.InvertViewportDrag) { gInputDragLast = newDragCoords; @@ -1086,7 +1090,7 @@ static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, WindowBase* w, W gPressedWidget.widget_index = widgetIndex; _inputFlags |= INPUT_FLAG_WIDGET_PRESSED; _inputState = InputState::WidgetPressed; - _clickRepeatTicks = 1; + _clickRepeatTicks = gCurrentRealTimeTicks; WidgetInvalidateByNumber(windowClass, windowNumber, widgetIndex); WindowEventMouseDownCall(w, widgetIndex); @@ -1304,17 +1308,28 @@ void InputStateWidgetPressed( if (WidgetIsDisabled(*w, widgetIndex)) break; - if (_clickRepeatTicks != 0) + // If this variable is non-zero then its the last tick the mouse down event was fired. + if (_clickRepeatTicks.has_value()) { - _clickRepeatTicks++; + // The initial amount of time in ticks to wait until the first click repeat. + constexpr auto ticksUntilRepeats = 16U; - // Handle click repeat - if (_clickRepeatTicks >= 16 && (_clickRepeatTicks & 3) == 0) + // The amount of ticks between each click repeat. + constexpr auto eventDelayInTicks = 3U; + + // The amount of ticks since the last click repeat. + const auto clickRepeatsDelta = gCurrentRealTimeTicks - _clickRepeatTicks.value(); + + // Handle click repeat, only start this when at least 16 ticks elapsed. + if (clickRepeatsDelta >= ticksUntilRepeats && (clickRepeatsDelta & eventDelayInTicks) == 0) { if (WidgetIsHoldable(*w, widgetIndex)) { WindowEventMouseDownCall(w, widgetIndex); } + + // Subtract initial delay from here on we want the event each third tick. + _clickRepeatTicks = gCurrentRealTimeTicks - ticksUntilRepeats; } } @@ -1439,7 +1454,7 @@ void InputStateWidgetPressed( return; } - _clickRepeatTicks = 0; + _clickRepeatTicks = std::nullopt; if (_inputState != InputState::DropdownActive) { // Hold down widget and drag outside of area?? diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index df93445e00..a1231638a0 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -1112,12 +1112,12 @@ namespace OpenRCT2 { Tick(); - // Always run this at a fixed rate, Update can cause multiple ticks if the game is speed up. - WindowUpdateAll(); - _ticksAccumulator -= GAME_UPDATE_TIME_MS; } + ContextHandleInput(); + WindowUpdateAll(); + if (ShouldDraw()) { Draw(); @@ -1141,9 +1141,6 @@ namespace OpenRCT2 Tick(); - // Always run this at a fixed rate, Update can cause multiple ticks if the game is speed up. - WindowUpdateAll(); - _ticksAccumulator -= GAME_UPDATE_TIME_MS; // Get the next position of each sprite @@ -1151,6 +1148,9 @@ namespace OpenRCT2 tweener.PostTick(); } + ContextHandleInput(); + WindowUpdateAll(); + if (shouldDraw) { const float alpha = std::min(_ticksAccumulator / GAME_UPDATE_TIME_MS, 1.0f); diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp index e42246cc45..fe7794137c 100644 --- a/src/openrct2/GameState.cpp +++ b/src/openrct2/GameState.cpp @@ -230,8 +230,6 @@ void GameState::Tick() gWindowMapFlashingFlags &= ~MapFlashingFlags::StaffListOpen; ContextUpdateMapTooltip(); - - ContextHandleInput(); } // Always perform autosave check, even when paused