feat: improve Windows mouse input for menus and containers

Add robust keyboard/mouse menu focus/click handling, keep container cursor input responsive under capture, and align input consumption rules to prevent gameplay and UI from stealing each other’s mouse events.
This commit is contained in:
deadman96385 2026-03-01 12:11:07 -06:00
parent b865228eed
commit 287d43a255
10 changed files with 490 additions and 162 deletions

View file

@ -21,6 +21,7 @@ IUIScene_AbstractContainerMenu::IUIScene_AbstractContainerMenu()
m_pointerPos.x = 0.0f;
m_pointerPos.y = 0.0f;
m_bMousePointerControl = false;
}
@ -63,6 +64,7 @@ void IUIScene_AbstractContainerMenu::Initialize(int iPad, AbstractContainerMenu*
m_eMaxSection = maxSection;
m_iConsectiveInputTicks = 0;
m_bMousePointerControl = false;
m_bNavigateBack = bNavigateBack;
@ -463,6 +465,25 @@ void IUIScene_AbstractContainerMenu::onMouseTick()
}
#endif
if (m_bMousePointerControl)
{
// Force pointer to the externally provided mouse position so any
// residual stick input/drift cannot desync the software cursor.
vPointerPos = m_pointerPos;
vPointerPos.x += m_fPointerImageOffsetX;
vPointerPos.y += m_fPointerImageOffsetY;
bStickInput = true;
m_iConsectiveInputTicks = 0;
m_eCurrTapState = eTapStateNoInput;
#ifdef USE_POINTER_ACCEL
m_fPointerVelX = 0.0f;
m_fPointerVelY = 0.0f;
m_fPointerAccelX = 0.0f;
m_fPointerAccelY = 0.0f;
#endif
}
// Determine which slot the pointer is currently over.
ESceneSection eSectionUnderPointer = eSectionNone;
int iNewSlotX = -1;
@ -689,10 +710,13 @@ void IUIScene_AbstractContainerMenu::onMouseTick()
}
// Clamp to pointer extents.
if ( vPointerPos.x < m_fPointerMinX ) vPointerPos.x = m_fPointerMinX;
else if ( vPointerPos.x > m_fPointerMaxX ) vPointerPos.x = m_fPointerMaxX;
if ( vPointerPos.y < m_fPointerMinY ) vPointerPos.y = m_fPointerMinY;
else if ( vPointerPos.y > m_fPointerMaxY ) vPointerPos.y = m_fPointerMaxY;
if (!m_bMousePointerControl)
{
if ( vPointerPos.x < m_fPointerMinX ) vPointerPos.x = m_fPointerMinX;
else if ( vPointerPos.x > m_fPointerMaxX ) vPointerPos.x = m_fPointerMaxX;
if ( vPointerPos.y < m_fPointerMinY ) vPointerPos.y = m_fPointerMinY;
else if ( vPointerPos.y > m_fPointerMaxY ) vPointerPos.y = m_fPointerMaxY;
}
// Check if the pointer is outside of the panel.
bool bPointerIsOutsidePanel = false;

View file

@ -158,6 +158,7 @@ protected:
Slot *m_lastPointerLabelSlot;
bool m_bSplitscreen;
bool m_bMousePointerControl;
bool m_bNavigateBack; // should we exit the xuiscenes or just navigate back on exit?
virtual bool IsSectionSlotList( ESceneSection eSection ) { return eSection != eSectionNone; }

View file

@ -210,6 +210,18 @@ UIController::UIController()
m_currentRenderViewport = C4JRender::VIEWPORT_TYPE_FULLSCREEN;
m_bCustomRenderPosition = false;
m_winUserIndex = 0;
#ifdef _WINDOWS64
m_menuMouseOkPressed = false;
m_menuMouseOkReleased = false;
m_menuMousePageUpPressed = false;
m_menuMousePageDownPressed = false;
m_menuMouseFocusMovie = NULL;
m_menuMouseFocusObject = IGGY_FOCUS_NULL;
m_menuMouseX = -1;
m_menuMouseY = -1;
#endif
m_accumulatedTicks = 0;
InitializeCriticalSection(&m_navigationLock);
@ -694,6 +706,10 @@ void UIController::tickInput()
void UIController::handleInput()
{
#ifdef _WINDOWS64
handleMouseMenuInput();
#endif
// For each user, loop over each key type and send messages based on the state
for(unsigned int iPad = 0; iPad < XUSER_MAX_COUNT; ++iPad)
{
@ -753,6 +769,264 @@ void UIController::handleInput()
#endif
}
#ifdef _WINDOWS64
void UIController::handleMouseMenuInput()
{
m_menuMouseOkPressed = false;
m_menuMouseOkReleased = false;
m_menuMousePageUpPressed = false;
m_menuMousePageDownPressed = false;
if (KMInput.IsCaptured() || !GetMenuDisplayed(0))
{
m_menuMouseFocusMovie = NULL;
m_menuMouseFocusObject = IGGY_FOCUS_NULL;
m_menuMouseX = -1;
m_menuMouseY = -1;
return;
}
m_menuMouseOkPressed = KMInput.IsMousePressed(0);
m_menuMouseOkReleased = KMInput.IsMouseReleased(0);
int wheel = KMInput.ConsumeScrollDelta();
if (wheel > 0) m_menuMousePageUpPressed = true;
else if (wheel < 0) m_menuMousePageDownPressed = true;
if (IsContainerMenuDisplayed(0))
{
m_menuMouseFocusMovie = NULL;
m_menuMouseFocusObject = IGGY_FOCUS_NULL;
m_menuMouseX = -1;
m_menuMouseY = -1;
return;
}
EUIGroup group = eUIGroup_Fullscreen;
UIScene *scene = getMouseFocusableScene(group);
if (!scene || !scene->hasMovie())
{
m_menuMouseFocusMovie = NULL;
m_menuMouseFocusObject = IGGY_FOCUS_NULL;
m_menuMouseX = -1;
m_menuMouseY = -1;
return;
}
int clientWidth = 0;
int clientHeight = 0;
if (!KMInput.GetClientSize(clientWidth, clientHeight) || clientWidth <= 0 || clientHeight <= 0)
{
return;
}
int clientX = 0;
int clientY = 0;
if (!KMInput.GetMouseClientPosition(clientX, clientY))
{
return;
}
S32 uiX = (S32)((float)clientX * (m_fScreenWidth / (float)clientWidth));
S32 uiY = (S32)((float)clientY * (m_fScreenHeight / (float)clientHeight));
S32 viewportX = 0;
S32 viewportY = 0;
S32 viewportW = 0;
S32 viewportH = 0;
m_groups[(int)group]->getRenderDimensions(viewportW, viewportH);
getViewportOffset(m_groups[(int)group]->GetViewportType(), viewportX, viewportY);
if (viewportW <= 0 || viewportH <= 0)
{
return;
}
S32 localX = uiX - viewportX;
S32 localY = uiY - viewportY;
if (localX < 0 || localY < 0 || localX >= viewportW || localY >= viewportH)
{
return;
}
Iggy *movie = scene->getMovie();
IggyFocusableObject focusableObjects[256];
S32 numFocusableObjects = 0;
IggyFocusHandle currentFocus = IGGY_FOCUS_NULL;
if (!IggyPlayerGetFocusableObjects(movie, &currentFocus, focusableObjects, 256, &numFocusableObjects))
{
return;
}
S32 mouseX = (S32)((float)localX * ((float)scene->getRenderWidth() / (float)viewportW));
S32 mouseY = (S32)((float)localY * ((float)scene->getRenderHeight() / (float)viewportH));
IggyFocusHandle bestFocus = IGGY_FOCUS_NULL;
float bestArea = 3.4e38f;
for (S32 i = 0; i < numFocusableObjects; ++i)
{
const IggyFocusableObject &obj = focusableObjects[i];
float minX = min(obj.x0, obj.x1);
float maxX = max(obj.x0, obj.x1);
float minY = min(obj.y0, obj.y1);
float maxY = max(obj.y0, obj.y1);
if ((float)mouseX < minX || (float)mouseX > maxX || (float)mouseY < minY || (float)mouseY > maxY)
{
continue;
}
float area = max(1.0f, (maxX - minX) * (maxY - minY));
if (area <= bestArea)
{
bestArea = area;
bestFocus = obj.object;
}
}
if (bestFocus == IGGY_FOCUS_NULL)
{
float fallbackBestArea = 3.4e38f;
for (S32 i = 0; i < numFocusableObjects; ++i)
{
const IggyFocusableObject &obj = focusableObjects[i];
float minX = min(obj.x0, obj.x1);
float maxX = max(obj.x0, obj.x1);
float minY = min(obj.y0, obj.y1);
float maxY = max(obj.y0, obj.y1);
if ((float)localX < minX || (float)localX > maxX || (float)localY < minY || (float)localY > maxY)
{
continue;
}
float area = max(1.0f, (maxX - minX) * (maxY - minY));
if (area <= fallbackBestArea)
{
fallbackBestArea = area;
bestFocus = obj.object;
}
}
if (bestFocus != IGGY_FOCUS_NULL)
{
mouseX = localX;
mouseY = localY;
}
}
IggyEvent mouseEvent;
IggyEventResult eventResult;
IggyMakeEventMouseMove(&mouseEvent, mouseX, mouseY);
IggyPlayerDispatchEventRS(movie, &mouseEvent, &eventResult);
if (movie == m_menuMouseFocusMovie && mouseX == m_menuMouseX && mouseY == m_menuMouseY)
{
return;
}
bool movieChanged = (movie != m_menuMouseFocusMovie);
m_menuMouseFocusMovie = movie;
m_menuMouseX = mouseX;
m_menuMouseY = mouseY;
if (movieChanged) m_menuMouseFocusObject = IGGY_FOCUS_NULL;
if (bestFocus != IGGY_FOCUS_NULL && bestFocus != m_menuMouseFocusObject)
{
IggyPlayerSetFocusRS(movie, bestFocus, 0);
}
m_menuMouseFocusObject = bestFocus;
}
UIScene *UIController::getMouseFocusableScene(EUIGroup &group) const
{
static const EUILayer kLayerOrder[] =
{
eUILayer_Error,
eUILayer_Alert,
eUILayer_Popup,
eUILayer_Scene,
eUILayer_Fullscreen,
};
for (int groupIndex = 0; groupIndex < eUIGroup_COUNT; ++groupIndex)
{
EUIGroup testGroup = (EUIGroup)groupIndex;
for (int layerIndex = 0; layerIndex < (int)(sizeof(kLayerOrder) / sizeof(kLayerOrder[0])); ++layerIndex)
{
UIScene *scene = m_groups[groupIndex]->GetTopScene(kLayerOrder[layerIndex]);
if (!scene || !scene->canHandleInput() || !scene->hasMovie()) continue;
if (isContainerSceneType(scene->getSceneType())) continue;
group = testGroup;
return scene;
}
}
return NULL;
}
void UIController::getViewportOffset(C4JRender::eViewportType viewport, S32 &xPos, S32 &yPos) const
{
xPos = 0;
yPos = 0;
const float screenWidth = m_fScreenWidth;
const float screenHeight = m_fScreenHeight;
switch (viewport)
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
xPos = (S32)(screenWidth / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
xPos = (S32)(screenWidth / 4);
yPos = (S32)(screenHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
yPos = (S32)(screenHeight / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
xPos = (S32)(screenWidth / 2);
yPos = (S32)(screenHeight / 4);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
xPos = (S32)(screenWidth / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
yPos = (S32)(screenHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos = (S32)(screenWidth / 2);
yPos = (S32)(screenHeight / 2);
break;
default:
break;
}
}
bool UIController::isContainerSceneType(EUIScene sceneType) const
{
switch (sceneType)
{
case eUIScene_Crafting2x2Menu:
case eUIScene_Crafting3x3Menu:
case eUIScene_FurnaceMenu:
case eUIScene_ContainerMenu:
case eUIScene_LargeContainerMenu:
case eUIScene_InventoryMenu:
case eUIScene_DispenserMenu:
case eUIScene_CreativeMenu:
case eUIScene_EnchantingMenu:
case eUIScene_BrewingStandMenu:
case eUIScene_TradingMenu:
case eUIScene_AnvilMenu:
return true;
default:
return false;
}
}
#endif
void UIController::handleKeyPress(unsigned int iPad, unsigned int key)
{
@ -924,17 +1198,23 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key)
if (iPad == 0)
{
bool kbDown = false, kbPressed = false, kbReleased = false;
bool capturedContainerMenu = IsContainerMenuDisplayed(0) && KMInput.IsCaptured();
bool capturedMouseOkDown = capturedContainerMenu && KMInput.IsMouseDown(0);
bool capturedMouseOkPressed = capturedContainerMenu && KMInput.IsMousePressed(0);
bool capturedMouseOkReleased = capturedContainerMenu && KMInput.IsMouseReleased(0);
switch(key)
{
case ACTION_MENU_UP: kbDown = KMInput.IsKeyDown(VK_UP); kbPressed = KMInput.IsKeyPressed(VK_UP); kbReleased = KMInput.IsKeyReleased(VK_UP); break;
case ACTION_MENU_DOWN: kbDown = KMInput.IsKeyDown(VK_DOWN); kbPressed = KMInput.IsKeyPressed(VK_DOWN); kbReleased = KMInput.IsKeyReleased(VK_DOWN); break;
case ACTION_MENU_LEFT: kbDown = KMInput.IsKeyDown(VK_LEFT); kbPressed = KMInput.IsKeyPressed(VK_LEFT); kbReleased = KMInput.IsKeyReleased(VK_LEFT); break;
case ACTION_MENU_RIGHT: kbDown = KMInput.IsKeyDown(VK_RIGHT); kbPressed = KMInput.IsKeyPressed(VK_RIGHT); kbReleased = KMInput.IsKeyReleased(VK_RIGHT); break;
case ACTION_MENU_OK: kbDown = KMInput.IsKeyDown(VK_RETURN); kbPressed = KMInput.IsKeyPressed(VK_RETURN); kbReleased = KMInput.IsKeyReleased(VK_RETURN); break;
case ACTION_MENU_A: kbDown = KMInput.IsKeyDown(VK_RETURN); kbPressed = KMInput.IsKeyPressed(VK_RETURN); kbReleased = KMInput.IsKeyReleased(VK_RETURN); break;
case ACTION_MENU_CANCEL: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_B: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_PAUSEMENU: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_RIGHT: kbDown = KMInput.IsKeyDown(VK_RIGHT); kbPressed = KMInput.IsKeyPressed(VK_RIGHT); kbReleased = KMInput.IsKeyReleased(VK_RIGHT); break;
case ACTION_MENU_OK: kbDown = KMInput.IsKeyDown(VK_RETURN) || capturedMouseOkDown; kbPressed = KMInput.IsKeyPressed(VK_RETURN) || m_menuMouseOkPressed || capturedMouseOkPressed; kbReleased = KMInput.IsKeyReleased(VK_RETURN) || m_menuMouseOkReleased || capturedMouseOkReleased; break;
case ACTION_MENU_A: kbDown = KMInput.IsKeyDown(VK_RETURN) || capturedMouseOkDown; kbPressed = KMInput.IsKeyPressed(VK_RETURN) || m_menuMouseOkPressed || capturedMouseOkPressed; kbReleased = KMInput.IsKeyReleased(VK_RETURN) || m_menuMouseOkReleased || capturedMouseOkReleased; break;
case ACTION_MENU_CANCEL: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_B: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_PAUSEMENU: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break;
case ACTION_MENU_PAGEUP: kbPressed = m_menuMousePageUpPressed; break;
case ACTION_MENU_PAGEDOWN: kbPressed = m_menuMousePageDownPressed; break;
}
pressed = pressed || kbPressed;
released = released || kbReleased;
@ -3035,4 +3315,4 @@ void UIController::SendTouchInput(unsigned int iPad, unsigned int key, bool bPre
}
#endif
#endif

View file

@ -131,6 +131,18 @@ private:
vector<QueuedMessageBoxData *> m_queuedMessageBoxData;
unsigned int m_winUserIndex;
#ifdef _WINDOWS64
bool m_menuMouseOkPressed;
bool m_menuMouseOkReleased;
bool m_menuMousePageUpPressed;
bool m_menuMousePageDownPressed;
Iggy *m_menuMouseFocusMovie;
IggyFocusHandle m_menuMouseFocusObject;
int m_menuMouseX;
int m_menuMouseY;
#endif
//bool m_bSysUIShowing;
bool m_bSystemUIShowing;
C4JThread *m_reloadSkinThread;
@ -208,6 +220,12 @@ private:
void tickInput();
void handleInput();
void handleKeyPress(unsigned int iPad, unsigned int key);
#ifdef _WINDOWS64
void handleMouseMenuInput();
UIScene *getMouseFocusableScene(EUIGroup &group) const;
void getViewportOffset(C4JRender::eViewportType viewport, S32 &xPos, S32 &yPos) const;
bool isContainerSceneType(EUIScene sceneType) const;
#endif
protected:
static rrbool RADLINK ExternalFunctionCallback( void * user_callback_data , Iggy * player , IggyExternalFunctionCallUTF16 * call );

View file

@ -5,8 +5,63 @@
#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"
static void GetViewportRect(C4JRender::eViewportType viewport, S32 fullWidth, S32 fullHeight, S32 &xPos, S32 &yPos, S32 &width, S32 &height)
{
xPos = 0;
yPos = 0;
width = fullWidth;
height = fullHeight;
switch (viewport)
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
xPos = width / 4;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
xPos = width / 4;
yPos = height / 2;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
yPos = height / 4;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
xPos = width / 2;
yPos = height / 4;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
xPos = width / 2;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
yPos = height / 2;
width /= 2;
height /= 2;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos = width / 2;
yPos = height / 2;
width /= 2;
height /= 2;
break;
default:
break;
}
}
#endif
UIScene_AbstractContainerMenu::UIScene_AbstractContainerMenu(int iPad, UILayer *parentLayer) : UIScene(iPad, parentLayer)
@ -28,9 +83,6 @@ 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()
@ -112,8 +164,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<43>4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation<6F>4 development tool,
//0 to 753: JDX-1000x series controllers for the PlayStation<6F>4 development tool,)
//Y coordinate of the touch point (0 to 941: DUALSHOCK<43>4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation<6F>4 development tool,)
//0 to 753: JDX-1000x series controllers for the PlayStation<6F>4 development tool,)
m_fTouchPadMulX=fPanelWidth/1919.0f;
m_fTouchPadMulY=fPanelHeight/941.0f;
m_fTouchPadDeadZoneX=15.0f*m_fTouchPadMulX;
@ -160,8 +212,8 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex)
IggyEvent mouseEvent;
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
S32 x = m_pointerPos.x*((float)width/m_movieWidth);
S32 y = m_pointerPos.y*((float)height/m_movieHeight);
S32 x = m_pointerPos.x*((float)width/m_renderWidth);
S32 y = m_pointerPos.y*((float)height/m_renderHeight);
IggyMakeEventMouseMove( &mouseEvent, x, y);
IggyEventResult result;
@ -179,118 +231,61 @@ void UIScene_AbstractContainerMenu::tick()
{
UIScene::tick();
m_bMousePointerControl = false;
#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)
if (getPad() == 0)
{
RECT clientRect;
GetClientRect(KMInput.GetHWnd(), &clientRect);
int clientWidth = clientRect.right;
int clientHeight = clientRect.bottom;
if (clientWidth > 0 && clientHeight > 0)
if (KMInput.IsCaptured())
{
int mouseX = KMInput.GetMouseX();
int mouseY = KMInput.GetMouseY();
float dx = 0.0f;
float dy = 0.0f;
KMInput.ConsumeMouseDelta(dx, dy);
// 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);
const float pointerSensitivity = 1.00f;
m_pointerPos.x += dx * pointerSensitivity;
m_pointerPos.y += dy * pointerSensitivity;
m_bMousePointerControl = true;
}
else
{
int clientX = 0;
int clientY = 0;
int clientWidth = 0;
int clientHeight = 0;
if (KMInput.GetMouseClientPosition(clientX, clientY) && KMInput.GetClientSize(clientWidth, clientHeight) && clientWidth > 0 && clientHeight > 0)
{
S32 viewportX = 0;
S32 viewportY = 0;
S32 viewportW = 0;
S32 viewportH = 0;
GetViewportRect(m_parentLayer->m_parentGroup->GetViewportType(), (S32)clientWidth, (S32)clientHeight, viewportX, viewportY, viewportW, viewportH);
m_pointerPos.x = mx;
m_pointerPos.y = my;
rawMouseMovieX = mx;
rawMouseMovieY = my;
if (viewportW > 0 && viewportH > 0)
{
S32 localX = (S32)clientX - viewportX;
S32 localY = (S32)clientY - viewportY;
if (localX >= 0 && localY >= 0 && localX < viewportW && localY < viewportH)
{
float pointerX = ((float)localX / (float)viewportW) * (float)m_renderWidth;
float pointerY = ((float)localY / (float)viewportH) * (float)m_renderHeight;
m_pointerPos.x = pointerX;
m_pointerPos.y = pointerY;
m_bMousePointerControl = true;
}
}
}
}
}
#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 page scrolling
int scrollDelta = KMInput.ConsumeScrollDelta();
if (scrollDelta > 0)
{
handleKeyDown(m_iPad, ACTION_MENU_OTHER_STICK_UP, false);
}
else if (scrollDelta < 0)
{
handleKeyDown(m_iPad, ACTION_MENU_OTHER_STICK_DOWN, 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);
S32 x = m_pointerPos.x*((float)width/m_renderWidth);
S32 y = m_pointerPos.y*((float)height/m_renderHeight);
#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

View file

@ -138,7 +138,7 @@ void Input::tick(LocalPlayer *player)
// Mouse look is now handled per-frame in Minecraft::applyFrameMouseLook()
// to eliminate the 20Hz tick delay. Only flush any remaining delta here
// as a safety measure.
if (iPad == 0 && KMInput.IsCaptured())
if (iPad == 0 && KMInput.IsCaptured() && !ui.GetMenuDisplayed(iPad))
{
float rawDx, rawDy;
KMInput.ConsumeMouseDelta(rawDx, rawDy);

View file

@ -1238,6 +1238,7 @@ void Minecraft::applyFrameMouseLook()
if (iPad != 0) continue; // Mouse only applies to pad 0
if (!KMInput.IsCaptured()) continue;
if (ui.GetMenuDisplayed(iPad)) continue;
if (localgameModes[iPad] == NULL) continue;
float rawDx, rawDy;
@ -1250,6 +1251,12 @@ void Minecraft::applyFrameMouseLook()
if (app.GetGameSettings(iPad, eGameSetting_ControlInvertLook))
mdy = -mdy;
bool canLookYaw = localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_LEFT) || localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_RIGHT);
bool canLookPitch = localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_UP) || localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_DOWN);
if (!canLookYaw) mdx = 0.0f;
if (!canLookPitch) mdy = 0.0f;
if (mdx == 0.0f && mdy == 0.0f) continue;
// Apply 0.15f scaling (same as Entity::interpolateTurn / Entity::turn)
float dyaw = mdx * 0.15f;
float dpitch = -mdy * 0.15f;
@ -3274,22 +3281,32 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures)
// Mouse scroll wheel for hotbar
if (iPad == 0)
{
int kbWheel = KMInput.ConsumeScrollDelta();
int kbWheel = 0;
if (KMInput.IsCaptured())
{
kbWheel = KMInput.ConsumeScrollDelta();
}
else if (!ui.GetMenuDisplayed(0))
{
KMInput.ConsumeScrollDelta();
}
if (kbWheel > 0 && gameMode->isInputAllowed(MINECRAFT_ACTION_LEFT_SCROLL)) wheel += 1;
else if (kbWheel < 0 && gameMode->isInputAllowed(MINECRAFT_ACTION_RIGHT_SCROLL)) wheel -= 1;
// 1-9 keys for direct hotbar selection
if (gameMode->isInputAllowed(MINECRAFT_ACTION_LEFT_SCROLL))
for (int k = '1'; k <= '9'; k++)
{
for (int k = '1'; k <= '9'; k++)
if (!KMInput.ConsumeKeyPress(k))
{
if (KMInput.ConsumeKeyPress(k))
{
player->inventory->selected = k - '1';
app.SetOpacityTimer(iPad);
break;
}
continue;
}
if (KMInput.IsCaptured() && gameMode->isInputAllowed(MINECRAFT_ACTION_LEFT_SCROLL))
{
player->inventory->selected = k - '1';
app.SetOpacityTimer(iPad);
}
break;
}
}
#endif

View file

@ -216,6 +216,31 @@ int KeyboardMouseInput::ConsumeScrollDelta()
return delta;
}
bool KeyboardMouseInput::GetMouseClientPosition(int &x, int &y) const
{
if (!m_hWnd) return false;
POINT cursor;
if (!GetCursorPos(&cursor)) return false;
if (!ScreenToClient(m_hWnd, &cursor)) return false;
x = cursor.x;
y = cursor.y;
return true;
}
bool KeyboardMouseInput::GetClientSize(int &width, int &height) const
{
if (!m_hWnd) return false;
RECT rect;
if (!GetClientRect(m_hWnd, &rect)) return false;
width = rect.right - rect.left;
height = rect.bottom - rect.top;
return true;
}
// Mouse capture
void KeyboardMouseInput::SetCapture(bool capture)
{

View file

@ -45,6 +45,8 @@ public:
bool ConsumeMouseRelease(int btn);
void ConsumeMouseDelta(float &dx, float &dy);
int ConsumeScrollDelta();
bool GetMouseClientPosition(int &x, int &y) const;
bool GetClientSize(int &width, int &height) const;
// Absolute cursor position (client-area coordinates, for GUI when not captured)
void OnMouseMove(int x, int y);

View file

@ -395,9 +395,6 @@ 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);
@ -407,15 +404,6 @@ 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);
}
@ -606,7 +594,6 @@ app.DebugPrintf("width: %d, height: %d\n", width, height);
// Create a depth stencil buffer
D3D11_TEXTURE2D_DESC descDepth;
ZeroMemory(&descDepth, sizeof(descDepth));
descDepth.Width = width;
descDepth.Height = height;
@ -622,7 +609,6 @@ app.DebugPrintf("width: %d, height: %d\n", width, height);
hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencilBuffer);
D3D11_DEPTH_STENCIL_VIEW_DESC descDSView;
ZeroMemory(&descDSView, sizeof(descDSView));
descDSView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
descDSView.Texture2D.MipSlice = 0;
@ -716,26 +702,6 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
WCHAR exePath[MAX_PATH] = { 0 };
GetModuleFileNameW(NULL, exePath, MAX_PATH);
WCHAR* lastSlash = wcsrchr(exePath, L'\\');
if (lastSlash) {
*lastSlash = L'\0';
WCHAR devCheckPath[MAX_PATH] = { 0 };
swprintf_s(devCheckPath, MAX_PATH, L"%s\\..\\..\\Minecraft.Client\\Minecraft.Client.vcxproj", exePath);
if (GetFileAttributesW(devCheckPath) != INVALID_FILE_ATTRIBUTES) {
WCHAR projectPath[MAX_PATH] = { 0 };
swprintf_s(projectPath, MAX_PATH, L"%s\\..\\..\\Minecraft.Client", exePath);
SetCurrentDirectoryW(projectPath);
}
else {
SetCurrentDirectoryW(exePath);
}
}
// Declare DPI awareness so GetSystemMetrics returns physical pixels
SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
@ -1216,7 +1182,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) && pMinecraft->screen == NULL;
bool shouldCapture = app.GetGameStarted() && (!ui.GetMenuDisplayed(0) || ui.IsContainerMenuDisplayed(0));
// Left Alt key toggles capture on/off for debugging
if (KMInput.IsKeyPressed(VK_MENU))
{
@ -1526,4 +1492,4 @@ void MemPixStuff()
PIXAddNamedCounter(((float)allSectsTotal)/(4096.0f),"MemSect total pages");
}
#endif
#endif