From 47e00f7b62b0717d09504d63e9d53404250609de Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Mon, 2 Mar 2026 00:04:54 +0800 Subject: [PATCH] feat: improve mouse input handling --- .../UI/UIScene_AbstractContainerMenu.cpp | 115 +++++++++++++++++- .../Common/UI/UIScene_AbstractContainerMenu.h | 3 + Minecraft.Client/Screen.cpp | 69 +++++++++-- .../Windows64/KeyboardMouseInput.cpp | 23 ++++ .../Windows64/KeyboardMouseInput.h | 12 ++ .../Windows64/Windows64_Minecraft.cpp | 14 ++- Minecraft.Client/stdafx.h | 1 + Minecraft.Client/stubs.cpp | 41 +++++++ Minecraft.Client/stubs.h | 12 ++ README.md | 6 +- 10 files changed, 284 insertions(+), 12 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp index a1bd8270..942009d7 100644 --- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp @@ -5,6 +5,9 @@ #include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h" #include "..\..\..\Minecraft.World\net.minecraft.world.item.h" #include "..\..\MultiplayerLocalPlayer.h" +#ifdef _WINDOWS64 +#include "..\..\Windows64\KeyboardMouseInput.h" +#endif UIScene_AbstractContainerMenu::UIScene_AbstractContainerMenu(int iPad, UILayer *parentLayer) : UIScene(iPad, parentLayer) { @@ -25,6 +28,9 @@ UIScene_AbstractContainerMenu::UIScene_AbstractContainerMenu(int iPad, UILayer * ui.OverrideSFX(m_iPad,ACTION_MENU_DOWN,true); m_bIgnoreInput=false; +#ifdef _WINDOWS64 + m_bMouseDragSlider=false; +#endif } UIScene_AbstractContainerMenu::~UIScene_AbstractContainerMenu() @@ -106,8 +112,8 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex) #ifdef __ORBIS__ // we need to map the touchpad rectangle to the UI rectangle. While it works great for the creative menu, it is much too sensitive for the smaller menus. //X coordinate of the touch point (0 to 1919) - //Y coordinate of the touch point (0 to 941: DUALSHOCK®4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation®4 development tool, - //0 to 753: JDX-1000x series controllers for the PlayStation®4 development tool,) + //Y coordinate of the touch point (0 to 941: DUALSHOCK�4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation�4 development tool, + //0 to 753: JDX-1000x series controllers for the PlayStation�4 development tool,) m_fTouchPadMulX=fPanelWidth/1919.0f; m_fTouchPadMulY=fPanelHeight/941.0f; m_fTouchPadDeadZoneX=15.0f*m_fTouchPadMulX; @@ -173,13 +179,118 @@ void UIScene_AbstractContainerMenu::tick() { UIScene::tick(); +#ifdef _WINDOWS64 + bool mouseActive = (m_iPad == 0 && !KMInput.IsCaptured()); + float rawMouseMovieX = 0, rawMouseMovieY = 0; + // Map Windows mouse position to the virtual pointer in movie coordinates + if (mouseActive) + { + RECT clientRect; + GetClientRect(KMInput.GetHWnd(), &clientRect); + int clientWidth = clientRect.right; + int clientHeight = clientRect.bottom; + if (clientWidth > 0 && clientHeight > 0) + { + int mouseX = KMInput.GetMouseX(); + int mouseY = KMInput.GetMouseY(); + + // Convert mouse position to movie coordinates using the movie/client ratio + float mx = (float)mouseX * ((float)m_movieWidth / (float)clientWidth); + float my = (float)mouseY * ((float)m_movieHeight / (float)clientHeight); + + m_pointerPos.x = mx; + m_pointerPos.y = my; + rawMouseMovieX = mx; + rawMouseMovieY = my; + } + } +#endif + onMouseTick(); +#ifdef _WINDOWS64 + // Dispatch mouse clicks AFTER onMouseTick() has updated m_eCurrSection from the new pointer position + if (mouseActive) + { + if (KMInput.ConsumeMousePress(0)) + { + if (m_eCurrSection == eSectionInventoryCreativeSlider) + { + // Scrollbar click: use raw mouse position (onMouseTick may have snapped m_pointerPos) + m_bMouseDragSlider = true; + m_pointerPos.x = rawMouseMovieX; + m_pointerPos.y = rawMouseMovieY; + handleOtherClicked(m_iPad, eSectionInventoryCreativeSlider, 0, false); + } + else + { + handleKeyDown(m_iPad, ACTION_MENU_A, false); + } + } + else if (m_bMouseDragSlider && KMInput.IsMouseDown(0)) + { + // Continue scrollbar drag: update scroll position from current mouse Y + m_pointerPos.x = rawMouseMovieX; + m_pointerPos.y = rawMouseMovieY; + handleOtherClicked(m_iPad, eSectionInventoryCreativeSlider, 0, false); + } + + if (!KMInput.IsMouseDown(0)) + m_bMouseDragSlider = false; + + if (KMInput.ConsumeMousePress(1)) + { + handleKeyDown(m_iPad, ACTION_MENU_X, false); + } + if (KMInput.ConsumeMousePress(2)) + { + handleKeyDown(m_iPad, ACTION_MENU_Y, false); + } + + // Mouse scroll wheel for tab switching + int scrollDelta = KMInput.ConsumeScrollDelta(); + if (scrollDelta > 0) + { + handleKeyDown(m_iPad, ACTION_MENU_LEFT_SCROLL, false); + } + else if (scrollDelta < 0) + { + handleKeyDown(m_iPad, ACTION_MENU_RIGHT_SCROLL, false); + } + + // ESC to close — must be last since it may destroy this scene + if (KMInput.ConsumeKeyPress(VK_ESCAPE)) + { + handleKeyDown(m_iPad, ACTION_MENU_B, false); + return; + } + } +#endif + IggyEvent mouseEvent; S32 width, height; m_parentLayer->getRenderDimensions(width, height); + +#ifdef _WINDOWS64 + S32 x, y; + if (mouseActive) + { + // Send raw mouse position directly as Iggy event to avoid coordinate round-trip errors + // Scale mouse client coords to the Iggy display space (which was set to getRenderDimensions()) + RECT clientRect; + GetClientRect(KMInput.GetHWnd(), &clientRect); + x = (S32)((float)KMInput.GetMouseX() * ((float)width / (float)clientRect.right)); + y = (S32)((float)KMInput.GetMouseY() * ((float)height / (float)clientRect.bottom)); + } + else + { + x = (S32)(m_pointerPos.x * ((float)width / m_movieWidth)); + y = (S32)(m_pointerPos.y * ((float)height / m_movieHeight)); + } +#else S32 x = m_pointerPos.x*((float)width/m_movieWidth); S32 y = m_pointerPos.y*((float)height/m_movieHeight); +#endif IggyMakeEventMouseMove( &mouseEvent, x, y); // 4J Stu - This seems to be broken on Durango, so do it ourself diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h index b98b3763..26688dc5 100644 --- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h @@ -10,6 +10,9 @@ class UIScene_AbstractContainerMenu : public UIScene, public virtual IUIScene_Ab private: ESceneSection m_focusSection; bool m_bIgnoreInput; +#ifdef _WINDOWS64 + bool m_bMouseDragSlider; +#endif protected: UIControl m_controlMainPanel; diff --git a/Minecraft.Client/Screen.cpp b/Minecraft.Client/Screen.cpp index 0019b54d..6a402b04 100644 --- a/Minecraft.Client/Screen.cpp +++ b/Minecraft.Client/Screen.cpp @@ -5,6 +5,9 @@ #include "Tesselator.h" #include "Textures.h" #include "..\Minecraft.World\SoundTypes.h" +#ifdef _WINDOWS64 +#include "Windows64\KeyboardMouseInput.h" +#endif @@ -103,20 +106,71 @@ void Screen::init() void Screen::updateEvents() { +#ifdef _WINDOWS64 + // Poll mouse button state and dispatch click/release events + for (int btn = 0; btn < 3; btn++) + { + if (KMInput.ConsumeMousePress(btn)) + { + int xm = Mouse::getX() * width / minecraft->width; + int ym = height - Mouse::getY() * height / minecraft->height - 1; + mouseClicked(xm, ym, btn); + } + if (KMInput.ConsumeMouseRelease(btn)) + { + int xm = Mouse::getX() * width / minecraft->width; + int ym = height - Mouse::getY() * height / minecraft->height - 1; + mouseReleased(xm, ym, btn); + } + } + + // Poll keyboard events + for (int vk = 0; vk < 256; vk++) + { + if (KMInput.ConsumeKeyPress(vk)) + { + // Map Windows virtual key to the Keyboard constants used by Screen::keyPressed + int mappedKey = -1; + wchar_t ch = 0; + if (vk == VK_ESCAPE) mappedKey = Keyboard::KEY_ESCAPE; + else if (vk == VK_RETURN) mappedKey = Keyboard::KEY_RETURN; + else if (vk == VK_BACK) mappedKey = Keyboard::KEY_BACK; + else if (vk == VK_UP) mappedKey = Keyboard::KEY_UP; + else if (vk == VK_DOWN) mappedKey = Keyboard::KEY_DOWN; + else if (vk == VK_LEFT) mappedKey = Keyboard::KEY_LEFT; + else if (vk == VK_RIGHT) mappedKey = Keyboard::KEY_RIGHT; + else if (vk == VK_LSHIFT || vk == VK_RSHIFT) mappedKey = Keyboard::KEY_LSHIFT; + else if (vk == VK_TAB) mappedKey = Keyboard::KEY_TAB; + else if (vk >= 'A' && vk <= 'Z') + { + ch = (wchar_t)(vk - 'A' + L'a'); + if (KMInput.IsKeyDown(VK_SHIFT)) ch = (wchar_t)vk; + } + else if (vk >= '0' && vk <= '9') ch = (wchar_t)vk; + else if (vk == VK_SPACE) ch = L' '; + + if (mappedKey != -1) keyPressed(ch, mappedKey); + else if (ch != 0) keyPressed(ch, -1); + } + } +#else /* 4J - TODO - while (Mouse.next()) { - mouseEvent(); - } + while (Mouse.next()) { + mouseEvent(); + } - while (Keyboard.next()) { - keyboardEvent(); - } + while (Keyboard.next()) { + keyboardEvent(); + } */ - +#endif } void Screen::mouseEvent() { +#ifdef _WINDOWS64 + // Mouse event dispatching is handled directly in updateEvents() for Windows +#else /* 4J - TODO if (Mouse.getEventButtonState()) { int xm = Mouse.getEventX() * width / minecraft.width; @@ -128,6 +182,7 @@ void Screen::mouseEvent() mouseReleased(xm, ym, Mouse.getEventButton()); } */ +#endif } void Screen::keyboardEvent() diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp index 791eb272..fc763394 100644 --- a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp @@ -13,6 +13,8 @@ KeyboardMouseInput::KeyboardMouseInput() , m_captured(false) , m_hWnd(NULL) , m_initialized(false) + , m_mouseX(0) + , m_mouseY(0) { memset(m_keyState, 0, sizeof(m_keyState)); memset(m_keyStatePrev, 0, sizeof(m_keyStatePrev)); @@ -20,6 +22,7 @@ KeyboardMouseInput::KeyboardMouseInput() memset(m_mouseButtonsPrev, 0, sizeof(m_mouseButtonsPrev)); memset(m_keyPressedAccum, 0, sizeof(m_keyPressedAccum)); memset(m_mousePressedAccum, 0, sizeof(m_mousePressedAccum)); + memset(m_mouseReleasedAccum, 0, sizeof(m_mouseReleasedAccum)); } KeyboardMouseInput::~KeyboardMouseInput() @@ -103,6 +106,7 @@ void KeyboardMouseInput::OnMouseButton(int button, bool down) if (button >= 0 && button < 3) { if (down && !m_mouseButtons[button]) m_mousePressedAccum[button] = true; + if (!down && m_mouseButtons[button]) m_mouseReleasedAccum[button] = true; m_mouseButtons[button] = down; } } @@ -112,12 +116,23 @@ void KeyboardMouseInput::OnMouseWheel(int delta) m_scrollDeltaAccum += delta; } +void KeyboardMouseInput::OnMouseMove(int x, int y) +{ + m_mouseX = x; + m_mouseY = y; +} + +int KeyboardMouseInput::GetMouseX() const { return m_mouseX; } +int KeyboardMouseInput::GetMouseY() const { return m_mouseY; } +HWND KeyboardMouseInput::GetHWnd() const { return m_hWnd; } + void KeyboardMouseInput::ClearAllState() { memset(m_keyState, 0, sizeof(m_keyState)); memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); memset(m_keyPressedAccum, 0, sizeof(m_keyPressedAccum)); memset(m_mousePressedAccum, 0, sizeof(m_mousePressedAccum)); + memset(m_mouseReleasedAccum, 0, sizeof(m_mouseReleasedAccum)); m_mouseDeltaXAccum = 0.0f; m_mouseDeltaYAccum = 0.0f; m_scrollDeltaAccum = 0; @@ -178,6 +193,14 @@ bool KeyboardMouseInput::ConsumeMousePress(int btn) return pressed; } +bool KeyboardMouseInput::ConsumeMouseRelease(int btn) +{ + if (btn < 0 || btn >= 3) return false; + bool released = m_mouseReleasedAccum[btn]; + m_mouseReleasedAccum[btn] = false; + return released; +} + void KeyboardMouseInput::ConsumeMouseDelta(float &dx, float &dy) { dx = m_mouseDeltaXAccum; diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.h b/Minecraft.Client/Windows64/KeyboardMouseInput.h index 4d272f91..a09843f9 100644 --- a/Minecraft.Client/Windows64/KeyboardMouseInput.h +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.h @@ -42,9 +42,16 @@ public: // Use these from code that runs at game tick rate (20Hz). bool ConsumeKeyPress(int vk); bool ConsumeMousePress(int btn); + bool ConsumeMouseRelease(int btn); void ConsumeMouseDelta(float &dx, float &dy); int ConsumeScrollDelta(); + // Absolute cursor position (client-area coordinates, for GUI when not captured) + void OnMouseMove(int x, int y); + int GetMouseX() const; + int GetMouseY() const; + HWND GetHWnd() const; + // Mouse capture for FPS look void SetCapture(bool capture); bool IsCaptured() const; @@ -61,6 +68,7 @@ private: // Sticky press accumulators (persist until consumed by game tick) bool m_keyPressedAccum[256]; bool m_mousePressedAccum[3]; + bool m_mouseReleasedAccum[3]; // Mouse delta accumulators (persist until consumed by game tick) float m_mouseDeltaXAccum; @@ -72,6 +80,10 @@ private: bool m_captured; HWND m_hWnd; bool m_initialized; + + // Absolute cursor position in client coordinates + int m_mouseX; + int m_mouseY; }; extern KeyboardMouseInput KMInput; diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 6c6cec15..931e7f17 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -395,6 +395,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_MOUSEWHEEL: KMInput.OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); break; + case WM_MOUSEMOVE: + KMInput.OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; case WM_ACTIVATE: if (LOWORD(wParam) == WA_INACTIVE) KMInput.SetCapture(false); @@ -404,6 +407,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) KMInput.ClearAllState(); break; + case WM_SETCURSOR: + // Hide the OS cursor when an Iggy/Flash menu is displayed (it has its own Flash cursor) + if (LOWORD(lParam) == HTCLIENT && !KMInput.IsCaptured() && ui.GetMenuDisplayed(0)) + { + SetCursor(NULL); + return TRUE; + } + return DefWindowProc(hWnd, message, wParam, lParam); + default: return DefWindowProc(hWnd, message, wParam, lParam); } @@ -1184,7 +1196,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Update mouse capture: capture when in-game and no menu is open { static bool altToggleSuppressCapture = false; - bool shouldCapture = app.GetGameStarted() && !ui.GetMenuDisplayed(0); + bool shouldCapture = app.GetGameStarted() && !ui.GetMenuDisplayed(0) && pMinecraft->screen == NULL; // Left Alt key toggles capture on/off for debugging if (KMInput.IsKeyPressed(VK_MENU)) { diff --git a/Minecraft.Client/stdafx.h b/Minecraft.Client/stdafx.h index 60cd4667..4ae4250e 100644 --- a/Minecraft.Client/stdafx.h +++ b/Minecraft.Client/stdafx.h @@ -66,6 +66,7 @@ typedef unsigned __int64 __uint64; #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: #include +#include #include #include // TODO: reference additional headers your program requires here diff --git a/Minecraft.Client/stubs.cpp b/Minecraft.Client/stubs.cpp index 0c7e680a..16215ba2 100644 --- a/Minecraft.Client/stubs.cpp +++ b/Minecraft.Client/stubs.cpp @@ -1,5 +1,46 @@ #include "stdafx.h" +#ifdef _WINDOWS64 +#include "Windows64\KeyboardMouseInput.h" + +int Mouse::getX() +{ + return KMInput.GetMouseX(); +} + +int Mouse::getY() +{ + // Return Y in bottom-up coordinates (OpenGL convention, matching original Java LWJGL Mouse) + RECT rect; + GetClientRect(KMInput.GetHWnd(), &rect); + return (rect.bottom - 1) - KMInput.GetMouseY(); +} + +bool Mouse::isButtonDown(int button) +{ + return KMInput.IsMouseDown(button); +} + +bool Keyboard::isKeyDown(int key) +{ + // Map Keyboard constants to Windows virtual key codes + if (key == Keyboard::KEY_LSHIFT) return KMInput.IsKeyDown(VK_LSHIFT); + if (key == Keyboard::KEY_RSHIFT) return KMInput.IsKeyDown(VK_RSHIFT); + if (key == Keyboard::KEY_ESCAPE) return KMInput.IsKeyDown(VK_ESCAPE); + if (key == Keyboard::KEY_RETURN) return KMInput.IsKeyDown(VK_RETURN); + if (key == Keyboard::KEY_BACK) return KMInput.IsKeyDown(VK_BACK); + if (key == Keyboard::KEY_SPACE) return KMInput.IsKeyDown(VK_SPACE); + if (key == Keyboard::KEY_TAB) return KMInput.IsKeyDown(VK_TAB); + if (key == Keyboard::KEY_UP) return KMInput.IsKeyDown(VK_UP); + if (key == Keyboard::KEY_DOWN) return KMInput.IsKeyDown(VK_DOWN); + if (key == Keyboard::KEY_LEFT) return KMInput.IsKeyDown(VK_LEFT); + if (key == Keyboard::KEY_RIGHT) return KMInput.IsKeyDown(VK_RIGHT); + if (key >= Keyboard::KEY_A && key <= Keyboard::KEY_Z) + return KMInput.IsKeyDown('A' + (key - Keyboard::KEY_A)); + return false; +} +#endif + void glReadPixels(int,int, int, int, int, int, ByteBuffer *) { } diff --git a/Minecraft.Client/stubs.h b/Minecraft.Client/stubs.h index f9196e20..cc788867 100644 --- a/Minecraft.Client/stubs.h +++ b/Minecraft.Client/stubs.h @@ -186,7 +186,11 @@ class Keyboard public: static void create() {} static void destroy() {} +#ifdef _WINDOWS64 + static bool isKeyDown(int key); +#else static bool isKeyDown(int) {return false;} +#endif static wstring getKeyName(int) { return L"KEYNAME"; } static void enableRepeatEvents(bool) {} static const int KEY_A = 0; @@ -224,6 +228,8 @@ public: static const int KEY_UP = 32; static const int KEY_DOWN = 33; static const int KEY_TAB = 34; + static const int KEY_LEFT = 35; + static const int KEY_RIGHT = 36; }; class Mouse @@ -231,9 +237,15 @@ class Mouse public: static void create() {} static void destroy() {} +#ifdef _WINDOWS64 + static int getX(); + static int getY(); + static bool isButtonDown(int button); +#else static int getX() { return 0; } static int getY() { return 0; } static bool isButtonDown(int) { return false; } +#endif }; class Display diff --git a/README.md b/README.md index e9af7d0f..279f7051 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # MinecraftConsoles +[![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](https://discord.gg/5CSzhc9t) + ![img.png](img.png) ## Introduction @@ -20,9 +22,9 @@ This project contains the source code of Minecraft Legacy Console Edition v1.3.0 2. Clone the repository 3. Open the project by double-clicking `MinecraftConsoles.sln` 4. Make sure `Minecraft.Client` is set as the Startup Project -5. Set the build configuration to **Debug** or **Release** and the target platform to **Windows64**, then build and run +5. Set the build configuration to **Debug** (Release is also OK but has some bugs) and the target platform to **Windows64**, then build and run ## Known Issues - Builds for other platforms have not been tested and are most likely non-functional -- Other unknown issues may exist \ No newline at end of file +- There are some render bugs in the Release mode build