From a191df1f2e01a3fd14bea0b48436c6159cdcc47a Mon Sep 17 00:00:00 2001 From: itsRevela Date: Fri, 10 Apr 2026 21:38:43 -0500 Subject: [PATCH] fix: controller cursor speed no longer scales with framerate When framerate was uncapped (vsync off, high-end hardware), the controller cursor in the inventory and creative menus moved way too fast. Basically unusable unless you switched to the dpad. The cursor update was tied to how often the screen redraws, so the faster the game ran, the faster the cursor flew. Now the cursor moves a smaller distance per frame at higher framerates, so the actual on-screen speed stays the same whether you're at 60 FPS or 600 FPS. While fixing this I also found an old workaround that was rounding the cursor position to whole pixels every frame and nudging it by 1 pixel to keep it from getting stuck. That nudge was pointing the wrong way on the vertical axis, which made up/down movement feel broken once the per-frame distance got small. Removed the rounding and the nudge. The cursor can now hold a fractional position between frames, and the part of the code that actually draws the cursor still snaps it to whole pixels on screen. Fixes #3 --- .../UI/IUIScene_AbstractContainerMenu.cpp | 54 ++++++++----------- .../UI/IUIScene_AbstractContainerMenu.h | 2 + 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp index 918bd8e6..7c6d3c9c 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp @@ -29,6 +29,7 @@ IUIScene_AbstractContainerMenu::IUIScene_AbstractContainerMenu() m_pointerPos.y = 0.0f; m_bPointerDrivenByMouse = false; + m_iLastMouseTickTimeNs = -1; } IUIScene_AbstractContainerMenu::~IUIScene_AbstractContainerMenu() @@ -266,6 +267,21 @@ void IUIScene_AbstractContainerMenu::UpdateTooltips() void IUIScene_AbstractContainerMenu::onMouseTick() { + // Frame-rate independent cursor input, normalized to a 60Hz reference frame. + const int64_t kRefFrameNs = 1000000000LL / 60; + const int64_t kMinDeltaNs = 1000000LL; + const int64_t kMaxDeltaNs = 100000000LL; + int64_t iNowNs = System::nanoTime(); + float fFrameScale = 1.0f; + if ( m_iLastMouseTickTimeNs > 0 ) + { + int64_t iDeltaNs = iNowNs - m_iLastMouseTickTimeNs; + if ( iDeltaNs < kMinDeltaNs ) iDeltaNs = kMinDeltaNs; + if ( iDeltaNs > kMaxDeltaNs ) iDeltaNs = kMaxDeltaNs; + fFrameScale = static_cast(iDeltaNs) / static_cast(kRefFrameNs); + } + m_iLastMouseTickTimeNs = iNowNs; + Minecraft *pMinecraft = Minecraft::GetInstance(); if( pMinecraft->localgameModes[getPad()] != nullptr) { @@ -422,10 +438,10 @@ void IUIScene_AbstractContainerMenu::onMouseTick() // The SD/splitscreen scenes are approximately 0.6 times the size of the fullscreen on if(!RenderManager.IsHiDef() || app.GetLocalPlayerCount() > 1) fInputScale *= 0.6f; - fInputX *= fInputScale; - fInputY *= fInputScale; + fInputX *= fInputScale * fFrameScale; + fInputY *= fInputScale * fFrameScale; -#ifdef USE_POINTER_ACCEL +#ifdef USE_POINTER_ACCEL m_fPointerAccelX += fInputX / 50.0f; m_fPointerAccelY += fInputY / 50.0f; @@ -1269,36 +1285,8 @@ void IUIScene_AbstractContainerMenu::onMouseTick() vPointerPos.x -= m_fPointerImageOffsetX; vPointerPos.y -= m_fPointerImageOffsetY; - // Update pointer position. - // 4J-PB - do not allow sub pixel positions or we get broken lines in box edges - - // problem here when sensitivity is low - we'll be moving a sub pixel size, so it'll clamp, and we'll never move. In that case, move 1 pixel - if(fInputDirX!=0.0f) - { - if(fInputDirX==1.0f) - { - vPointerPos.x+=0.999999f; - } - else - { - vPointerPos.x-=0.999999f; - } - } - - if(fInputDirY!=0.0f) - { - if(fInputDirY==1.0f) - { - vPointerPos.y+=0.999999f; - } - else - { - vPointerPos.y-=0.999999f; - } - } - - vPointerPos.x = static_cast(floor(vPointerPos.x + 0.5f)); - vPointerPos.y = static_cast(floor(vPointerPos.y + 0.5f)); + // Keep sub-pixel float state so deltas <1px accumulate across frames; the renderer + // truncates to integer pixels when emitting the Iggy mouse event. m_pointerPos = vPointerPos; adjustPointerForSafeZone(); diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h index 6710e18f..4a4700f9 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h @@ -145,6 +145,8 @@ protected: int m_iConsectiveInputTicks; + int64_t m_iLastMouseTickTimeNs; + // Used for detecting quick "taps" in a direction, should jump cursor to next slot. enum ETapState {