// Copyright (c) 2012-2021 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 /// /// Implementation of the keyboard device for Windows platform. /// /// class WindowsKeyboard : public Keyboard { public: /// /// Initializes a new instance of the class. /// explicit WindowsKeyboard() : Keyboard() { } public: bool WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam); }; /// /// Implementation of the mouse device for Windows platform. /// /// class WindowsMouse : public Mouse { public: /// /// Initializes a new instance of the class. /// explicit WindowsMouse() : Mouse() { } public: bool WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam); public: // [Mouse] void SetMousePosition(const Vector2& newPosition) final override { ::SetCursorPos(static_cast(newPosition.X), static_cast(newPosition.Y)); OnMouseMoved(newPosition); } }; /// /// Implementation of the gamepad device for Windows platform. /// /// class WindowsGamepad : public Gamepad { private: Windows::DWORD _userIndex; public: /// /// Initializes a new instance of the class. /// /// The user index. explicit WindowsGamepad(Windows::DWORD userIndex); /// /// Finalizes an instance of the class. /// ~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; WindowsKeyboard Keyboard; } void WindowsInput::Init() { Input::Mouse = &WindowsInputImpl::Mouse; Input::Keyboard = &WindowsInputImpl::Keyboard; } 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(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(wParam), window); result = true; break; } case WM_KEYDOWN: case WM_SYSKEYDOWN: { OnKeyDown(static_cast(wParam), window); result = true; break; } case WM_KEYUP: case WM_SYSKEYUP: { OnKeyUp(static_cast(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(WINDOWS_GET_X_LPARAM(lParam)); p.y = static_cast(WINDOWS_GET_Y_LPARAM(lParam)); ::ClientToScreen(window->GetHWND(), &p); const auto mousePos = Vector2(static_cast(p.x), static_cast(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(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(leftMotor * 65535.0f); xInputVibration.wRightMotorSpeed = static_cast(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