Files
FlaxEngine/Source/Engine/Platform/Windows/WindowsInput.cpp
2022-06-13 00:40:32 +02:00

373 lines
12 KiB
C++

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#if PLATFORM_WINDOWS
#include "WindowsInput.h"
#include "WindowsWindow.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Input/Keyboard.h"
#include "Engine/Input/Gamepad.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "../Win32/IncludeWindowsHeaders.h"
#define DIRECTINPUT_VERSION 0x0800
#include <xinput.h>
/// <summary>
/// Implementation of the keyboard device for Windows platform.
/// </summary>
/// <seealso cref="Keyboard" />
class WindowsKeyboard : public Keyboard
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="WindowsKeyboard"/> class.
/// </summary>
explicit WindowsKeyboard()
: Keyboard()
{
}
public:
bool WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam);
};
/// <summary>
/// Implementation of the mouse device for Windows platform.
/// </summary>
/// <seealso cref="Mouse" />
class WindowsMouse : public Mouse
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="WindowsMouse"/> class.
/// </summary>
explicit WindowsMouse()
: Mouse()
{
}
public:
bool WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam);
public:
// [Mouse]
void SetMousePosition(const Float2& newPosition) final override
{
::SetCursorPos(static_cast<int32>(newPosition.X), static_cast<int32>(newPosition.Y));
OnMouseMoved(newPosition);
}
};
/// <summary>
/// Implementation of the gamepad device for Windows platform.
/// </summary>
/// <seealso cref="Gamepad" />
class WindowsGamepad : public Gamepad
{
private:
Windows::DWORD _userIndex;
public:
/// <summary>
/// Initializes a new instance of the <see cref="WindowsGamepad"/> class.
/// </summary>
/// <param name="userIndex">The user index.</param>
explicit WindowsGamepad(Windows::DWORD userIndex);
/// <summary>
/// Finalizes an instance of the <see cref="WindowsGamepad"/> class.
/// </summary>
~WindowsGamepad();
public:
// [Gamepad]
void SetVibration(const GamepadVibrationState& state) override;
protected:
// [Gamepad]
bool UpdateState() override;
};
namespace WindowsInputImpl
{
float XInputLastUpdateTime = 0;
bool XInputGamepads[XUSER_MAX_COUNT] = { false };
WindowsMouse* Mouse = nullptr;
WindowsKeyboard* Keyboard = nullptr;
}
void WindowsInput::Init()
{
Input::Mouse = WindowsInputImpl::Mouse = New<WindowsMouse>();
Input::Keyboard = WindowsInputImpl::Keyboard = New<WindowsKeyboard>();
}
void WindowsInput::Update()
{
// Update every second
const float time = (float)Platform::GetTimeSeconds();
if (time - WindowsInputImpl::XInputLastUpdateTime < 1.0f)
return;
WindowsInputImpl::XInputLastUpdateTime = time;
PROFILE_CPU_NAMED("Input.ScanGamepads");
XINPUT_STATE state;
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++)
{
if (!WindowsInputImpl::XInputGamepads[i])
{
const DWORD result = XInputGetState(i, &state);
if (result == ERROR_SUCCESS)
{
Input::Gamepads.Add(New<WindowsGamepad>(i));
Input::OnGamepadsChanged();
}
}
}
}
bool WindowsInput::WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam)
{
if (WindowsInputImpl::Mouse->WndProc(window, msg, wParam, lParam))
return true;
if (WindowsInputImpl::Keyboard->WndProc(window, msg, wParam, lParam))
return true;
return false;
}
bool WindowsKeyboard::WndProc(Window* window, const Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam)
{
bool result = false;
// Handle keyboard related messages
switch (msg)
{
case WM_CHAR:
{
OnCharInput(static_cast<Char>(wParam), window);
result = true;
break;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
OnKeyDown(static_cast<KeyboardKeys>(wParam), window);
result = true;
break;
}
case WM_KEYUP:
case WM_SYSKEYUP:
{
OnKeyUp(static_cast<KeyboardKeys>(wParam), window);
result = true;
break;
}
}
return result;
}
bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM lParam)
{
bool result = false;
// Get mouse position in screen coordinates
POINT p;
p.x = static_cast<LONG>(WINDOWS_GET_X_LPARAM(lParam));
p.y = static_cast<LONG>(WINDOWS_GET_Y_LPARAM(lParam));
::ClientToScreen(window->GetHWND(), &p);
const Float2 mousePos(static_cast<float>(p.x), static_cast<float>(p.y));
// Handle mouse related messages
switch (msg)
{
case WM_MOUSEMOVE:
{
OnMouseMove(mousePos, window);
result = true;
break;
}
case WM_MOUSELEAVE:
{
OnMouseLeave(window);
result = true;
break;
}
case WM_LBUTTONDOWN:
{
OnMouseDown(mousePos, MouseButton::Left, window);
result = true;
break;
}
case WM_RBUTTONDOWN:
{
OnMouseDown(mousePos, MouseButton::Right, window);
result = true;
break;
}
case WM_MBUTTONDOWN:
{
OnMouseDown(mousePos, MouseButton::Middle, window);
result = true;
break;
}
case WM_XBUTTONDOWN:
{
const auto button = (HIWORD(wParam) & XBUTTON1) ? MouseButton::Extended1 : MouseButton::Extended2;
OnMouseDown(mousePos, button, window);
result = true;
break;
}
case WM_LBUTTONUP:
{
OnMouseUp(mousePos, MouseButton::Left, window);
result = true;
break;
}
case WM_RBUTTONUP:
{
OnMouseUp(mousePos, MouseButton::Right, window);
result = true;
break;
}
case WM_MBUTTONUP:
{
OnMouseUp(mousePos, MouseButton::Middle, window);
result = true;
break;
}
case WM_XBUTTONUP:
{
const auto button = (HIWORD(wParam) & XBUTTON1) ? MouseButton::Extended1 : MouseButton::Extended2;
OnMouseUp(mousePos, button, window);
result = true;
break;
}
case WM_LBUTTONDBLCLK:
{
OnMouseDoubleClick(mousePos, MouseButton::Left, window);
result = true;
break;
}
case WM_RBUTTONDBLCLK:
{
OnMouseDoubleClick(mousePos, MouseButton::Right, window);
result = true;
break;
}
case WM_MBUTTONDBLCLK:
{
OnMouseDoubleClick(mousePos, MouseButton::Middle, window);
result = true;
break;
}
case WM_MOUSEWHEEL:
{
const short delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (delta != 0)
{
const float deltaNormalized = static_cast<float>(delta) / WHEEL_DELTA;
// Use cached mouse position, the input pos is sometimes wrong in Win32
OnMouseWheel(_state.MousePosition, deltaNormalized, window);
}
result = true;
break;
}
}
return result;
}
float NormalizeXInputAxis(const int16 axisVal)
{
// Normalize [-32768..32767] -> [-1..1]
const float norm = axisVal <= 0 ? 32768.0f : 32767.0f;
return float(axisVal) / norm;
}
WindowsGamepad::WindowsGamepad(Windows::DWORD userIndex)
: Gamepad(Guid(0, 0, 0, userIndex), TEXT("XInput Gamepad"))
, _userIndex(userIndex)
{
WindowsInputImpl::XInputGamepads[_userIndex] = true;
}
WindowsGamepad::~WindowsGamepad()
{
WindowsInputImpl::XInputGamepads[_userIndex] = false;
}
void WindowsGamepad::SetVibration(const GamepadVibrationState& state)
{
Gamepad::SetVibration(state);
XINPUT_VIBRATION xInputVibration;
const float leftMotor = Math::Saturate((state.LeftLarge + state.LeftSmall) * 0.5f);
const float rightMotor = Math::Saturate((state.RightLarge + state.RightSmall) * 0.5f);
xInputVibration.wLeftMotorSpeed = static_cast<WORD>(leftMotor * 65535.0f);
xInputVibration.wRightMotorSpeed = static_cast<WORD>(rightMotor * 65535.0f);
XInputSetState(_userIndex, &xInputVibration);
}
bool WindowsGamepad::UpdateState()
{
// Gather XInput device state
XINPUT_STATE inputState;
if (XInputGetState(_userIndex, &inputState) != ERROR_SUCCESS)
{
return true;
}
// Process buttons state
_state.Buttons[(int32)GamepadButton::A] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_A);
_state.Buttons[(int32)GamepadButton::B] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_B);
_state.Buttons[(int32)GamepadButton::X] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_X);
_state.Buttons[(int32)GamepadButton::Y] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_Y);
_state.Buttons[(int32)GamepadButton::LeftShoulder] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
_state.Buttons[(int32)GamepadButton::RightShoulder] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
_state.Buttons[(int32)GamepadButton::Back] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_BACK);
_state.Buttons[(int32)GamepadButton::Start] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_START);
_state.Buttons[(int32)GamepadButton::LeftThumb] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
_state.Buttons[(int32)GamepadButton::RightThumb] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
_state.Buttons[(int32)GamepadButton::LeftTrigger] = inputState.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
_state.Buttons[(int32)GamepadButton::RightTrigger] = inputState.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
_state.Buttons[(int32)GamepadButton::DPadUp] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP);
_state.Buttons[(int32)GamepadButton::DPadDown] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN);
_state.Buttons[(int32)GamepadButton::DPadLeft] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT);
_state.Buttons[(int32)GamepadButton::DPadRight] = !!(inputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);
_state.Buttons[(int32)GamepadButton::LeftStickUp] = inputState.Gamepad.sThumbLY > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::LeftStickDown] = inputState.Gamepad.sThumbLY < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::LeftStickLeft] = inputState.Gamepad.sThumbLX < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::LeftStickRight] = inputState.Gamepad.sThumbLX > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::RightStickUp] = inputState.Gamepad.sThumbRY > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::RightStickDown] = inputState.Gamepad.sThumbRY < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::RightStickLeft] = inputState.Gamepad.sThumbRX < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
_state.Buttons[(int32)GamepadButton::RightStickRight] = inputState.Gamepad.sThumbRX > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
// Process axes state
_state.Axis[(int32)GamepadAxis::LeftStickX] = NormalizeXInputAxis(inputState.Gamepad.sThumbLX);
_state.Axis[(int32)GamepadAxis::LeftStickY] = NormalizeXInputAxis(inputState.Gamepad.sThumbLY);
_state.Axis[(int32)GamepadAxis::RightStickX] = NormalizeXInputAxis(inputState.Gamepad.sThumbRX);
_state.Axis[(int32)GamepadAxis::RightStickY] = NormalizeXInputAxis(inputState.Gamepad.sThumbRY);
_state.Axis[(int32)GamepadAxis::LeftTrigger] = inputState.Gamepad.bLeftTrigger / 255.0f;
_state.Axis[(int32)GamepadAxis::RightTrigger] = inputState.Gamepad.bRightTrigger / 255.0f;
return false;
}
#endif