feat: add support for keyboard and mouse input
This commit is contained in:
217
Minecraft.Client/Windows64/KeyboardMouseInput.cpp
Normal file
217
Minecraft.Client/Windows64/KeyboardMouseInput.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
#include "KeyboardMouseInput.h"
|
||||
|
||||
KeyboardMouseInput KMInput;
|
||||
|
||||
KeyboardMouseInput::KeyboardMouseInput()
|
||||
: m_mouseDeltaX(0.0f)
|
||||
, m_mouseDeltaY(0.0f)
|
||||
, m_mouseDeltaXAccum(0.0f)
|
||||
, m_mouseDeltaYAccum(0.0f)
|
||||
, m_scrollDelta(0)
|
||||
, m_scrollDeltaAccum(0)
|
||||
, m_captured(false)
|
||||
, m_hWnd(NULL)
|
||||
, m_initialized(false)
|
||||
{
|
||||
memset(m_keyState, 0, sizeof(m_keyState));
|
||||
memset(m_keyStatePrev, 0, sizeof(m_keyStatePrev));
|
||||
memset(m_mouseButtons, 0, sizeof(m_mouseButtons));
|
||||
memset(m_mouseButtonsPrev, 0, sizeof(m_mouseButtonsPrev));
|
||||
}
|
||||
|
||||
KeyboardMouseInput::~KeyboardMouseInput()
|
||||
{
|
||||
if (m_captured)
|
||||
{
|
||||
SetCapture(false);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::Init(HWND hWnd)
|
||||
{
|
||||
m_hWnd = hWnd;
|
||||
m_initialized = true;
|
||||
|
||||
// Register for raw mouse input
|
||||
RAWINPUTDEVICE rid;
|
||||
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
|
||||
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
|
||||
rid.dwFlags = 0;
|
||||
rid.hwndTarget = hWnd;
|
||||
RegisterRawInputDevices(&rid, 1, sizeof(rid));
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::Tick()
|
||||
{
|
||||
// Snapshot accumulated mouse deltas
|
||||
m_mouseDeltaX = m_mouseDeltaXAccum;
|
||||
m_mouseDeltaY = m_mouseDeltaYAccum;
|
||||
m_mouseDeltaXAccum = 0.0f;
|
||||
m_mouseDeltaYAccum = 0.0f;
|
||||
|
||||
// Snapshot scroll delta
|
||||
m_scrollDelta = m_scrollDeltaAccum;
|
||||
m_scrollDeltaAccum = 0;
|
||||
|
||||
// Keep cursor pinned to center while captured
|
||||
if (m_captured)
|
||||
CenterCursor();
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::EndFrame()
|
||||
{
|
||||
// Advance previous state for next frame's edge detection.
|
||||
// Must be called AFTER all consumers have read IsKeyPressed/Released etc.
|
||||
memcpy(m_keyStatePrev, m_keyState, sizeof(m_keyState));
|
||||
memcpy(m_mouseButtonsPrev, m_mouseButtons, sizeof(m_mouseButtons));
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::OnKeyDown(WPARAM vk)
|
||||
{
|
||||
if (vk < 256)
|
||||
{
|
||||
m_keyState[vk] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::OnKeyUp(WPARAM vk)
|
||||
{
|
||||
if (vk < 256)
|
||||
{
|
||||
m_keyState[vk] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::OnRawMouseInput(LPARAM lParam)
|
||||
{
|
||||
if (!m_captured) return;
|
||||
|
||||
UINT dwSize = 0;
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
|
||||
|
||||
BYTE* lpb = (BYTE*)alloca(dwSize);
|
||||
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
|
||||
return;
|
||||
|
||||
RAWINPUT* raw = (RAWINPUT*)lpb;
|
||||
if (raw->header.dwType == RIM_TYPEMOUSE)
|
||||
{
|
||||
if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE)
|
||||
{
|
||||
m_mouseDeltaXAccum += (float)raw->data.mouse.lLastX;
|
||||
m_mouseDeltaYAccum += (float)raw->data.mouse.lLastY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::OnMouseButton(int button, bool down)
|
||||
{
|
||||
if (button >= 0 && button < 3)
|
||||
{
|
||||
m_mouseButtons[button] = down;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::OnMouseWheel(int delta)
|
||||
{
|
||||
m_scrollDeltaAccum += delta;
|
||||
}
|
||||
|
||||
void KeyboardMouseInput::ClearAllState()
|
||||
{
|
||||
memset(m_keyState, 0, sizeof(m_keyState));
|
||||
memset(m_mouseButtons, 0, sizeof(m_mouseButtons));
|
||||
m_mouseDeltaXAccum = 0.0f;
|
||||
m_mouseDeltaYAccum = 0.0f;
|
||||
m_scrollDeltaAccum = 0;
|
||||
}
|
||||
|
||||
// Key queries
|
||||
bool KeyboardMouseInput::IsKeyDown(int vk) const
|
||||
{
|
||||
if (vk < 0 || vk >= 256) return false;
|
||||
return m_keyState[vk];
|
||||
}
|
||||
|
||||
bool KeyboardMouseInput::IsKeyPressed(int vk) const
|
||||
{
|
||||
if (vk < 0 || vk >= 256) return false;
|
||||
return m_keyState[vk] && !m_keyStatePrev[vk];
|
||||
}
|
||||
|
||||
bool KeyboardMouseInput::IsKeyReleased(int vk) const
|
||||
{
|
||||
if (vk < 0 || vk >= 256) return false;
|
||||
return !m_keyState[vk] && m_keyStatePrev[vk];
|
||||
}
|
||||
|
||||
// Mouse button queries
|
||||
bool KeyboardMouseInput::IsMouseDown(int btn) const
|
||||
{
|
||||
if (btn < 0 || btn >= 3) return false;
|
||||
return m_mouseButtons[btn];
|
||||
}
|
||||
|
||||
bool KeyboardMouseInput::IsMousePressed(int btn) const
|
||||
{
|
||||
if (btn < 0 || btn >= 3) return false;
|
||||
return m_mouseButtons[btn] && !m_mouseButtonsPrev[btn];
|
||||
}
|
||||
|
||||
bool KeyboardMouseInput::IsMouseReleased(int btn) const
|
||||
{
|
||||
if (btn < 0 || btn >= 3) return false;
|
||||
return !m_mouseButtons[btn] && m_mouseButtonsPrev[btn];
|
||||
}
|
||||
|
||||
// Delta queries
|
||||
float KeyboardMouseInput::GetMouseDeltaX() const { return m_mouseDeltaX; }
|
||||
float KeyboardMouseInput::GetMouseDeltaY() const { return m_mouseDeltaY; }
|
||||
int KeyboardMouseInput::GetScrollDelta() const { return m_scrollDelta; }
|
||||
|
||||
// Mouse capture
|
||||
void KeyboardMouseInput::SetCapture(bool capture)
|
||||
{
|
||||
if (capture == m_captured) return;
|
||||
m_captured = capture;
|
||||
|
||||
if (capture)
|
||||
{
|
||||
ShowCursor(FALSE);
|
||||
RECT rect;
|
||||
GetClientRect(m_hWnd, &rect);
|
||||
POINT topLeft = { rect.left, rect.top };
|
||||
POINT bottomRight = { rect.right, rect.bottom };
|
||||
ClientToScreen(m_hWnd, &topLeft);
|
||||
ClientToScreen(m_hWnd, &bottomRight);
|
||||
RECT screenRect = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y };
|
||||
ClipCursor(&screenRect);
|
||||
CenterCursor();
|
||||
|
||||
// Flush accumulated deltas so the snap-to-center doesn't cause a jump
|
||||
m_mouseDeltaXAccum = 0.0f;
|
||||
m_mouseDeltaYAccum = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCursor(TRUE);
|
||||
ClipCursor(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyboardMouseInput::IsCaptured() const { return m_captured; }
|
||||
|
||||
void KeyboardMouseInput::CenterCursor()
|
||||
{
|
||||
RECT rect;
|
||||
GetClientRect(m_hWnd, &rect);
|
||||
POINT center = { (rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2 };
|
||||
ClientToScreen(m_hWnd, ¢er);
|
||||
SetCursorPos(center.x, center.y);
|
||||
}
|
||||
|
||||
#endif // _WINDOWS64
|
||||
76
Minecraft.Client/Windows64/KeyboardMouseInput.h
Normal file
76
Minecraft.Client/Windows64/KeyboardMouseInput.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// HID usage page and usage for raw input registration
|
||||
#ifndef HID_USAGE_PAGE_GENERIC
|
||||
#define HID_USAGE_PAGE_GENERIC ((USHORT)0x01)
|
||||
#endif
|
||||
#ifndef HID_USAGE_GENERIC_MOUSE
|
||||
#define HID_USAGE_GENERIC_MOUSE ((USHORT)0x02)
|
||||
#endif
|
||||
|
||||
class KeyboardMouseInput
|
||||
{
|
||||
public:
|
||||
KeyboardMouseInput();
|
||||
~KeyboardMouseInput();
|
||||
|
||||
void Init(HWND hWnd);
|
||||
void Tick();
|
||||
void EndFrame();
|
||||
|
||||
// Called from WndProc
|
||||
void OnKeyDown(WPARAM vk);
|
||||
void OnKeyUp(WPARAM vk);
|
||||
void OnRawMouseInput(LPARAM lParam);
|
||||
void OnMouseButton(int button, bool down);
|
||||
void OnMouseWheel(int delta);
|
||||
void ClearAllState();
|
||||
|
||||
// Key state queries (call after Tick)
|
||||
bool IsKeyDown(int vk) const;
|
||||
bool IsKeyPressed(int vk) const;
|
||||
bool IsKeyReleased(int vk) const;
|
||||
|
||||
// Mouse button queries: 0=left, 1=right, 2=middle
|
||||
bool IsMouseDown(int btn) const;
|
||||
bool IsMousePressed(int btn) const;
|
||||
bool IsMouseReleased(int btn) const;
|
||||
|
||||
// Mouse deltas (consumed each Tick)
|
||||
float GetMouseDeltaX() const;
|
||||
float GetMouseDeltaY() const;
|
||||
int GetScrollDelta() const;
|
||||
|
||||
// Mouse capture for FPS look
|
||||
void SetCapture(bool capture);
|
||||
bool IsCaptured() const;
|
||||
|
||||
private:
|
||||
void CenterCursor();
|
||||
|
||||
bool m_keyState[256];
|
||||
bool m_keyStatePrev[256];
|
||||
|
||||
bool m_mouseButtons[3];
|
||||
bool m_mouseButtonsPrev[3];
|
||||
|
||||
float m_mouseDeltaX;
|
||||
float m_mouseDeltaY;
|
||||
float m_mouseDeltaXAccum;
|
||||
float m_mouseDeltaYAccum;
|
||||
|
||||
int m_scrollDelta;
|
||||
int m_scrollDeltaAccum;
|
||||
|
||||
bool m_captured;
|
||||
HWND m_hWnd;
|
||||
bool m_initialized;
|
||||
};
|
||||
|
||||
extern KeyboardMouseInput KMInput;
|
||||
|
||||
#endif // _WINDOWS64
|
||||
@@ -339,6 +339,63 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
|
||||
// Keyboard/Mouse input handling
|
||||
case WM_KEYDOWN:
|
||||
if (!(lParam & 0x40000000)) // ignore auto-repeat
|
||||
KMInput.OnKeyDown(wParam);
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
KMInput.OnKeyUp(wParam);
|
||||
break;
|
||||
case WM_SYSKEYDOWN:
|
||||
if (wParam == VK_MENU) // Alt key
|
||||
{
|
||||
if (!(lParam & 0x40000000))
|
||||
KMInput.OnKeyDown(wParam);
|
||||
return 0; // prevent default Alt behavior
|
||||
}
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
case WM_SYSKEYUP:
|
||||
if (wParam == VK_MENU)
|
||||
{
|
||||
KMInput.OnKeyUp(wParam);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
case WM_INPUT:
|
||||
KMInput.OnRawMouseInput(lParam);
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
KMInput.OnMouseButton(0, true);
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
KMInput.OnMouseButton(0, false);
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
KMInput.OnMouseButton(1, true);
|
||||
break;
|
||||
case WM_RBUTTONUP:
|
||||
KMInput.OnMouseButton(1, false);
|
||||
break;
|
||||
case WM_MBUTTONDOWN:
|
||||
KMInput.OnMouseButton(2, true);
|
||||
break;
|
||||
case WM_MBUTTONUP:
|
||||
KMInput.OnMouseButton(2, false);
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
KMInput.OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam));
|
||||
break;
|
||||
case WM_ACTIVATE:
|
||||
if (LOWORD(wParam) == WA_INACTIVE)
|
||||
KMInput.SetCapture(false);
|
||||
break;
|
||||
case WM_KILLFOCUS:
|
||||
KMInput.SetCapture(false);
|
||||
KMInput.ClearAllState();
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
@@ -715,6 +772,9 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
// Set the number of possible joypad layouts that the user can switch between, and the number of actions
|
||||
InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
|
||||
|
||||
// Initialize keyboard/mouse input
|
||||
KMInput.Init(g_hWnd);
|
||||
|
||||
// Set the default joypad action mappings for Minecraft
|
||||
DefineActions();
|
||||
InputManager.SetJoypadMapVal(0,0);
|
||||
@@ -940,6 +1000,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
app.UpdateTime();
|
||||
PIXBeginNamedEvent(0,"Input manager tick");
|
||||
InputManager.Tick();
|
||||
KMInput.Tick();
|
||||
PIXEndNamedEvent();
|
||||
PIXBeginNamedEvent(0,"Profile manager tick");
|
||||
// ProfileManager.Tick();
|
||||
@@ -1067,6 +1128,27 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
RenderManager.Present();
|
||||
|
||||
ui.CheckMenuDisplayed();
|
||||
|
||||
// Update mouse capture: capture when in-game and no menu is open
|
||||
{
|
||||
static bool altToggleSuppressCapture = false;
|
||||
bool shouldCapture = app.GetGameStarted() && !ui.GetMenuDisplayed(0);
|
||||
// Left Alt key toggles capture on/off for debugging
|
||||
if (KMInput.IsKeyPressed(VK_MENU))
|
||||
{
|
||||
if (KMInput.IsCaptured()) { KMInput.SetCapture(false); altToggleSuppressCapture = true; }
|
||||
else if (shouldCapture) { KMInput.SetCapture(true); altToggleSuppressCapture = false; }
|
||||
}
|
||||
else if (!shouldCapture)
|
||||
{
|
||||
if (KMInput.IsCaptured()) KMInput.SetCapture(false);
|
||||
altToggleSuppressCapture = false;
|
||||
}
|
||||
else if (shouldCapture && !KMInput.IsCaptured() && GetFocus() == g_hWnd && !altToggleSuppressCapture)
|
||||
{
|
||||
KMInput.SetCapture(true);
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
PIXBeginNamedEvent(0,"Profile load check");
|
||||
// has the game defined profile data been changed (by a profile load)
|
||||
@@ -1158,6 +1240,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
// Fix for #7318 - Title crashes after short soak in the leaderboards menu
|
||||
// A memory leak was caused because the icon renderer kept creating new Vec3's because the pool wasn't reset
|
||||
Vec3::resetPool();
|
||||
|
||||
KMInput.EndFrame();
|
||||
}
|
||||
|
||||
// Free resources, unregister custom classes, and exit.
|
||||
|
||||
Reference in New Issue
Block a user