diff --git a/CMakeLists.txt b/CMakeLists.txt index ec5aa331f6..d43497e2cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1565,6 +1565,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Config.cpp Core/Config.h Core/ConfigValues.h + Core/ControlMapper.cpp + Core/ControlMapper.h Core/Core.cpp Core/Core.h Core/Compatibility.cpp diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp new file mode 100644 index 0000000000..253c7ce69e --- /dev/null +++ b/Core/ControlMapper.cpp @@ -0,0 +1,310 @@ +#include "Common/Math/math_util.h" +#include "Core/KeyMap.h" +#include "Core/ControlMapper.h" +#include "Core/Config.h" + +static float MapAxisValue(float v) { + const float deadzone = g_Config.fAnalogDeadzone; + const float invDeadzone = g_Config.fAnalogInverseDeadzone; + const float sensitivity = g_Config.fAnalogSensitivity; + const float sign = v >= 0.0f ? 1.0f : -1.0f; + return sign * Clamp(invDeadzone + (abs(v) - deadzone) / (1.0f - deadzone) * (sensitivity - invDeadzone), 0.0f, 1.0f); +} + +void ConvertAnalogStick(float &x, float &y) { + const bool isCircular = g_Config.bAnalogIsCircular; + + float norm = std::max(fabsf(x), fabsf(y)); + + if (norm == 0.0f) + return; + + if (isCircular) { + float newNorm = sqrtf(x * x + y * y); + float factor = newNorm / norm; + x *= factor; + y *= factor; + norm = newNorm; + } + + float mappedNorm = MapAxisValue(norm); + x = Clamp(x / norm * mappedNorm, -1.0f, 1.0f); + y = Clamp(y / norm * mappedNorm, -1.0f, 1.0f); +} + +void ControlMapper::SetCallbacks(std::function onVKeyDown, std::function onVKeyUp, std::function setPSPAxis) { + onVKeyDown_ = onVKeyDown; + onVKeyUp_ = onVKeyUp; + setPSPAxis_ = setPSPAxis; +} + +bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) { + std::vector pspKeys; + KeyMap::KeyToPspButton(key.deviceId, key.keyCode, &pspKeys); + + if (pspKeys.size() && (key.flags & KEY_IS_REPEAT)) { + // Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android. + return true; + } + + for (size_t i = 0; i < pspKeys.size(); i++) { + pspKey(pspKeys[i], key.flags); + } + + if (!pspKeys.size() || key.deviceId == DEVICE_ID_DEFAULT) { + if ((key.flags & KEY_DOWN) && key.keyCode == NKCODE_BACK) { + *pauseTrigger = true; + return true; + } + } + + return pspKeys.size() > 0; +} + +bool ControlMapper::Axis(const AxisInput &axis) { + if (axis.value > 0) { + processAxis(axis, 1); + return true; + } else if (axis.value < 0) { + processAxis(axis, -1); + return true; + } else if (axis.value == 0) { + // Both directions! Prevents sticking for digital input devices that are axises (like HAT) + processAxis(axis, 1); + processAxis(axis, -1); + return true; + } + return false; +} + + +inline bool IsAnalogStickKey(int key) { + switch (key) { + case VIRTKEY_AXIS_X_MIN: + case VIRTKEY_AXIS_X_MAX: + case VIRTKEY_AXIS_Y_MIN: + case VIRTKEY_AXIS_Y_MAX: + case VIRTKEY_AXIS_RIGHT_X_MIN: + case VIRTKEY_AXIS_RIGHT_X_MAX: + case VIRTKEY_AXIS_RIGHT_Y_MIN: + case VIRTKEY_AXIS_RIGHT_Y_MAX: + return true; + default: + return false; + } +} + +static int RotatePSPKeyCode(int x) { + switch (x) { + case CTRL_UP: return CTRL_RIGHT; + case CTRL_RIGHT: return CTRL_DOWN; + case CTRL_DOWN: return CTRL_LEFT; + case CTRL_LEFT: return CTRL_UP; + default: + return x; + } +} + +void ControlMapper::setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero) { + // The down events can repeat, so just trust the virtKeys array. + bool minDown = virtKeys[virtualKeyMin - VIRTKEY_FIRST]; + bool maxDown = virtKeys[virtualKeyMax - VIRTKEY_FIRST]; + + const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f; + float value = 0.0f; + if (minDown) + value -= scale; + if (maxDown) + value += scale; + if (setZero || minDown || maxDown) { + setPSPAxis_(axis, value, stick); + } +} + +void ControlMapper::pspKey(int pspKeyCode, int flags) { + int rotations = 0; + switch (g_Config.iInternalScreenRotation) { + case ROTATION_LOCKED_HORIZONTAL180: + rotations = 2; + break; + case ROTATION_LOCKED_VERTICAL: + rotations = 1; + break; + case ROTATION_LOCKED_VERTICAL180: + rotations = 3; + break; + } + + for (int i = 0; i < rotations; i++) { + pspKeyCode = RotatePSPKeyCode(pspKeyCode); + } + + if (pspKeyCode >= VIRTKEY_FIRST) { + int vk = pspKeyCode - VIRTKEY_FIRST; + if (flags & KEY_DOWN) { + virtKeys[vk] = true; + onVKeyDown(pspKeyCode); + } + if (flags & KEY_UP) { + virtKeys[vk] = false; + onVKeyUp(pspKeyCode); + } + } else { + // INFO_LOG(SYSTEM, "pspKey %i %i", pspKeyCode, flags); + if (flags & KEY_DOWN) + __CtrlButtonDown(pspKeyCode); + if (flags & KEY_UP) + __CtrlButtonUp(pspKeyCode); + } +} + +void ControlMapper::onVKeyDown(int vkey) { + switch (vkey) { + + case VIRTKEY_AXIS_X_MIN: + case VIRTKEY_AXIS_X_MAX: + setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX); + break; + case VIRTKEY_AXIS_Y_MIN: + case VIRTKEY_AXIS_Y_MAX: + setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX); + break; + + case VIRTKEY_AXIS_RIGHT_X_MIN: + case VIRTKEY_AXIS_RIGHT_X_MAX: + setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX); + break; + case VIRTKEY_AXIS_RIGHT_Y_MIN: + case VIRTKEY_AXIS_RIGHT_Y_MAX: + setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX); + break; + + case VIRTKEY_ANALOG_LIGHTLY: + setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false); + setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false); + setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false); + setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false); + break; + + default: + if (onVKeyDown_) + onVKeyDown_(vkey); + break; + } +} + +void ControlMapper::onVKeyUp(int vkey) { + switch (vkey) { + + case VIRTKEY_AXIS_X_MIN: + case VIRTKEY_AXIS_X_MAX: + setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX); + break; + case VIRTKEY_AXIS_Y_MIN: + case VIRTKEY_AXIS_Y_MAX: + setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX); + break; + + case VIRTKEY_AXIS_RIGHT_X_MIN: + case VIRTKEY_AXIS_RIGHT_X_MAX: + setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX); + break; + case VIRTKEY_AXIS_RIGHT_Y_MIN: + case VIRTKEY_AXIS_RIGHT_Y_MAX: + setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX); + break; + + case VIRTKEY_ANALOG_LIGHTLY: + setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false); + setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false); + setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false); + setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false); + break; + + default: + if (onVKeyUp_) + onVKeyUp_(vkey); + break; + } +} + +void ControlMapper::processAxis(const AxisInput &axis, int direction) { + // Sanity check + if (axis.axisId < 0 || axis.axisId >= JOYSTICK_AXIS_MAX) { + return; + } + + const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f; + + std::vector results; + KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, direction, &results); + + for (int result : results) { + float value = fabs(axis.value) * scale; + switch (result) { + case VIRTKEY_AXIS_X_MIN: + setPSPAxis_('X', -value, CTRL_STICK_LEFT); + break; + case VIRTKEY_AXIS_X_MAX: + setPSPAxis_('X', value, CTRL_STICK_LEFT); + break; + case VIRTKEY_AXIS_Y_MIN: + setPSPAxis_('Y', -value, CTRL_STICK_LEFT); + break; + case VIRTKEY_AXIS_Y_MAX: + setPSPAxis_('Y', value, CTRL_STICK_LEFT); + break; + + case VIRTKEY_AXIS_RIGHT_X_MIN: + setPSPAxis_('X', -value, CTRL_STICK_RIGHT); + break; + case VIRTKEY_AXIS_RIGHT_X_MAX: + setPSPAxis_('X', value, CTRL_STICK_RIGHT); + break; + case VIRTKEY_AXIS_RIGHT_Y_MIN: + setPSPAxis_('Y', -value, CTRL_STICK_RIGHT); + break; + case VIRTKEY_AXIS_RIGHT_Y_MAX: + setPSPAxis_('Y', value, CTRL_STICK_RIGHT); + break; + } + } + + std::vector resultsOpposite; + KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, -direction, &resultsOpposite); + + int axisState = 0; + float threshold = axis.deviceId == DEVICE_ID_MOUSE ? AXIS_BIND_THRESHOLD_MOUSE : AXIS_BIND_THRESHOLD; + if (direction == 1 && axis.value >= threshold) { + axisState = 1; + } else if (direction == -1 && axis.value <= -threshold) { + axisState = -1; + } else { + axisState = 0; + } + + if (axisState != axisState_[axis.axisId]) { + axisState_[axis.axisId] = axisState; + if (axisState != 0) { + for (size_t i = 0; i < results.size(); i++) { + if (!IsAnalogStickKey(results[i])) + pspKey(results[i], KEY_DOWN); + } + // Also unpress the other direction (unless both directions press the same key.) + for (size_t i = 0; i < resultsOpposite.size(); i++) { + if (!IsAnalogStickKey(resultsOpposite[i]) && std::find(results.begin(), results.end(), resultsOpposite[i]) == results.end()) + pspKey(resultsOpposite[i], KEY_UP); + } + } else if (axisState == 0) { + // Release both directions, trying to deal with some erratic controllers that can cause it to stick. + for (size_t i = 0; i < results.size(); i++) { + if (!IsAnalogStickKey(results[i])) + pspKey(results[i], KEY_UP); + } + for (size_t i = 0; i < resultsOpposite.size(); i++) { + if (!IsAnalogStickKey(resultsOpposite[i])) + pspKey(resultsOpposite[i], KEY_UP); + } + } + } +} diff --git a/Core/ControlMapper.h b/Core/ControlMapper.h new file mode 100644 index 0000000000..815499878b --- /dev/null +++ b/Core/ControlMapper.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Common/Input/InputState.h" +#include "Core/KeyMap.h" + +#include + +// Utilities for mapping input events to PSP inputs and virtual keys. +// Main use is of course from EmuScreen.cpp, but also useful from control settings etc. + + +// Maps analog stick input to a distorted space according to +// the deadzone and shape settings. +void ConvertAnalogStick(float &x, float &y); + + +class ControlMapper { +public: + bool Key(const KeyInput &key, bool *pauseTrigger); + bool Axis(const AxisInput &axis); + + void SetCallbacks( + std::function onVKeyDown, + std::function onVKeyUp, + std::function setPSPAxis); + +private: + void processAxis(const AxisInput &axis, int direction); + void pspKey(int pspKeyCode, int flags); + void setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero = true); + + void onVKeyDown(int vkey); + void onVKeyUp(int vkey); + + // To track mappable virtual keys. We can have as many as we want. + bool virtKeys[VIRTKEY_COUNT]{}; + + // De-noise mapped axis updates + int axisState_[JOYSTICK_AXIS_MAX]{}; + + // Callbacks + std::function onVKeyDown_; + std::function onVKeyUp_; + std::function setPSPAxis_; +}; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 797daeeeb9..d8d96a3f31 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -509,6 +509,7 @@ + @@ -1064,6 +1065,7 @@ + @@ -1398,4 +1400,4 @@ - + \ No newline at end of file diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 9102da39e0..486f2a2e10 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1175,6 +1175,9 @@ Debugger\WebSocket + + Core + @@ -1892,6 +1895,9 @@ Debugger\WebSocket + + Core + @@ -1918,4 +1924,4 @@ Ext\libzip - + \ No newline at end of file diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 799cc20da2..ca5466ff6e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -18,6 +18,9 @@ #include "ppsspp_config.h" #include +#include + +using namespace std::placeholders; #include "Common/Render/TextureAtlas.h" #include "Common/GPU/OpenGL/GLFeatures.h" @@ -93,6 +96,7 @@ static AVIDump avi; #endif +// TODO: Ugly! static bool frameStep_; static int lastNumFlips; static bool startDumping; @@ -129,14 +133,45 @@ static void __EmuScreenVblank() #endif } +// Handles control rotation due to internal screen rotation. +// TODO: This should be a callback too, so we don't actually call the __Ctrl functions +// from settings screens, etc. +static void SetPSPAxis(char axis, float value, int stick) { + switch (g_Config.iInternalScreenRotation) { + case ROTATION_LOCKED_HORIZONTAL: + // Standard rotation. + break; + case ROTATION_LOCKED_HORIZONTAL180: + value = -value; + break; + case ROTATION_LOCKED_VERTICAL: + value = axis == 'Y' ? value : -value; + axis = (axis == 'X') ? 'Y' : 'X'; + break; + case ROTATION_LOCKED_VERTICAL180: + value = axis == 'Y' ? -value : value; + axis = (axis == 'X') ? 'Y' : 'X'; + break; + default: + break; + } + if (axis == 'X') + __CtrlSetAnalogX(value, stick); + else if (axis == 'Y') + __CtrlSetAnalogY(value, stick); +} + EmuScreen::EmuScreen(const Path &filename) - : bootPending_(true), gamePath_(filename), invalid_(true), quit_(false), pauseTrigger_(false), saveStatePreviewShownTime_(0.0), saveStatePreview_(nullptr) { - memset(axisState_, 0, sizeof(axisState_)); + : gamePath_(filename) { saveStateSlot_ = SaveState::GetCurrentSlot(); __DisplayListenVblank(__EmuScreenVblank); frameStep_ = false; lastNumFlips = gpuStats.numFlips; startDumping = false; + controlMapper_.SetCallbacks( + std::bind(&EmuScreen::onVKeyDown, this, _1), + std::bind(&EmuScreen::onVKeyUp, this, _1), + &SetPSPAxis); // Make sure we don't leave it at powerdown after the last game. // TODO: This really should be handled elsewhere if it isn't. @@ -308,7 +343,6 @@ void EmuScreen::bootComplete() { osm.Show(sc->T("PressESC", "Press ESC to open the pause menu"), 3.0f); } #endif - memset(virtKeys, 0, sizeof(virtKeys)); #if !PPSSPP_PLATFORM(UWP) if (GetGPUBackend() == GPUBackend::OPENGL) { @@ -596,31 +630,6 @@ void EmuScreen::onVKeyDown(int virtualKeyCode) { } #endif - case VIRTKEY_AXIS_X_MIN: - case VIRTKEY_AXIS_X_MAX: - setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX); - break; - case VIRTKEY_AXIS_Y_MIN: - case VIRTKEY_AXIS_Y_MAX: - setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX); - break; - - case VIRTKEY_AXIS_RIGHT_X_MIN: - case VIRTKEY_AXIS_RIGHT_X_MAX: - setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX); - break; - case VIRTKEY_AXIS_RIGHT_Y_MIN: - case VIRTKEY_AXIS_RIGHT_Y_MAX: - setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX); - break; - - case VIRTKEY_ANALOG_LIGHTLY: - setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false); - setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false); - setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false); - setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false); - break; - case VIRTKEY_REWIND: if (SaveState::CanRewind()) { SaveState::Rewind(&AfterSaveStateAction); @@ -701,31 +710,6 @@ void EmuScreen::onVKeyUp(int virtualKeyCode) { } break; - case VIRTKEY_AXIS_X_MIN: - case VIRTKEY_AXIS_X_MAX: - setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX); - break; - case VIRTKEY_AXIS_Y_MIN: - case VIRTKEY_AXIS_Y_MAX: - setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX); - break; - - case VIRTKEY_AXIS_RIGHT_X_MIN: - case VIRTKEY_AXIS_RIGHT_X_MAX: - setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX); - break; - case VIRTKEY_AXIS_RIGHT_Y_MIN: - case VIRTKEY_AXIS_RIGHT_Y_MAX: - setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX); - break; - - case VIRTKEY_ANALOG_LIGHTLY: - setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false); - setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false); - setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false); - setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false); - break; - case VIRTKEY_RAPID_FIRE: __CtrlSetRapidFire(false); break; @@ -747,233 +731,16 @@ void EmuScreen::onVKeyUp(int virtualKeyCode) { } } -// Handles control rotation due to internal screen rotation. -static void SetPSPAxis(char axis, float value, int stick) { - switch (g_Config.iInternalScreenRotation) { - case ROTATION_LOCKED_HORIZONTAL: - // Standard rotation. - break; - case ROTATION_LOCKED_HORIZONTAL180: - value = -value; - break; - case ROTATION_LOCKED_VERTICAL: - value = axis == 'Y' ? value : -value; - axis = (axis == 'X') ? 'Y' : 'X'; - break; - case ROTATION_LOCKED_VERTICAL180: - value = axis == 'Y' ? -value : value; - axis = (axis == 'X') ? 'Y' : 'X'; - break; - default: - break; - } - if (axis == 'X') - __CtrlSetAnalogX(value, stick); - else if (axis == 'Y') - __CtrlSetAnalogY(value, stick); -} - -inline void EmuScreen::setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero) { - // The down events can repeat, so just trust the virtKeys array. - bool minDown = virtKeys[virtualKeyMin - VIRTKEY_FIRST]; - bool maxDown = virtKeys[virtualKeyMax - VIRTKEY_FIRST]; - - const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f; - float value = 0.0f; - if (minDown) - value -= scale; - if (maxDown) - value += scale; - if (setZero || minDown || maxDown) - SetPSPAxis(axis, value, stick); -} - bool EmuScreen::key(const KeyInput &key) { Core_NotifyActivity(); - std::vector pspKeys; - KeyMap::KeyToPspButton(key.deviceId, key.keyCode, &pspKeys); - - if (pspKeys.size() && (key.flags & KEY_IS_REPEAT)) { - // Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android. - return true; - } - - for (size_t i = 0; i < pspKeys.size(); i++) { - pspKey(pspKeys[i], key.flags); - } - - if (!pspKeys.size() || key.deviceId == DEVICE_ID_DEFAULT) { - if ((key.flags & KEY_DOWN) && key.keyCode == NKCODE_BACK) { - pauseTrigger_ = true; - return true; - } - } - - return pspKeys.size() > 0; -} - -static int RotatePSPKeyCode(int x) { - switch (x) { - case CTRL_UP: return CTRL_RIGHT; - case CTRL_RIGHT: return CTRL_DOWN; - case CTRL_DOWN: return CTRL_LEFT; - case CTRL_LEFT: return CTRL_UP; - default: - return x; - } -} - -void EmuScreen::pspKey(int pspKeyCode, int flags) { - int rotations = 0; - switch (g_Config.iInternalScreenRotation) { - case ROTATION_LOCKED_HORIZONTAL180: - rotations = 2; - break; - case ROTATION_LOCKED_VERTICAL: - rotations = 1; - break; - case ROTATION_LOCKED_VERTICAL180: - rotations = 3; - break; - } - - for (int i = 0; i < rotations; i++) { - pspKeyCode = RotatePSPKeyCode(pspKeyCode); - } - - if (pspKeyCode >= VIRTKEY_FIRST) { - int vk = pspKeyCode - VIRTKEY_FIRST; - if (flags & KEY_DOWN) { - virtKeys[vk] = true; - onVKeyDown(pspKeyCode); - } - if (flags & KEY_UP) { - virtKeys[vk] = false; - onVKeyUp(pspKeyCode); - } - } else { - // INFO_LOG(SYSTEM, "pspKey %i %i", pspKeyCode, flags); - if (flags & KEY_DOWN) - __CtrlButtonDown(pspKeyCode); - if (flags & KEY_UP) - __CtrlButtonUp(pspKeyCode); - } + return controlMapper_.Key(key, &pauseTrigger_); } bool EmuScreen::axis(const AxisInput &axis) { Core_NotifyActivity(); - if (axis.value > 0) { - processAxis(axis, 1); - return true; - } else if (axis.value < 0) { - processAxis(axis, -1); - return true; - } else if (axis.value == 0) { - // Both directions! Prevents sticking for digital input devices that are axises (like HAT) - processAxis(axis, 1); - processAxis(axis, -1); - return true; - } - return false; -} - -inline bool IsAnalogStickKey(int key) { - switch (key) { - case VIRTKEY_AXIS_X_MIN: - case VIRTKEY_AXIS_X_MAX: - case VIRTKEY_AXIS_Y_MIN: - case VIRTKEY_AXIS_Y_MAX: - case VIRTKEY_AXIS_RIGHT_X_MIN: - case VIRTKEY_AXIS_RIGHT_X_MAX: - case VIRTKEY_AXIS_RIGHT_Y_MIN: - case VIRTKEY_AXIS_RIGHT_Y_MAX: - return true; - default: - return false; - } -} - -void EmuScreen::processAxis(const AxisInput &axis, int direction) { - // Sanity check - if (axis.axisId < 0 || axis.axisId >= JOYSTICK_AXIS_MAX) { - return; - } - - const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f; - - std::vector results; - KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, direction, &results); - - for (int result : results) { - float value = fabs(axis.value) * scale; - switch (result) { - case VIRTKEY_AXIS_X_MIN: - SetPSPAxis('X', -value, CTRL_STICK_LEFT); - break; - case VIRTKEY_AXIS_X_MAX: - SetPSPAxis('X', value, CTRL_STICK_LEFT); - break; - case VIRTKEY_AXIS_Y_MIN: - SetPSPAxis('Y', -value, CTRL_STICK_LEFT); - break; - case VIRTKEY_AXIS_Y_MAX: - SetPSPAxis('Y', value, CTRL_STICK_LEFT); - break; - - case VIRTKEY_AXIS_RIGHT_X_MIN: - SetPSPAxis('X', -value, CTRL_STICK_RIGHT); - break; - case VIRTKEY_AXIS_RIGHT_X_MAX: - SetPSPAxis('X', value, CTRL_STICK_RIGHT); - break; - case VIRTKEY_AXIS_RIGHT_Y_MIN: - SetPSPAxis('Y', -value, CTRL_STICK_RIGHT); - break; - case VIRTKEY_AXIS_RIGHT_Y_MAX: - SetPSPAxis('Y', value, CTRL_STICK_RIGHT); - break; - } - } - - std::vector resultsOpposite; - KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, -direction, &resultsOpposite); - - int axisState = 0; - float threshold = axis.deviceId == DEVICE_ID_MOUSE ? AXIS_BIND_THRESHOLD_MOUSE : AXIS_BIND_THRESHOLD; - if (direction == 1 && axis.value >= threshold) { - axisState = 1; - } else if (direction == -1 && axis.value <= -threshold) { - axisState = -1; - } else { - axisState = 0; - } - - if (axisState != axisState_[axis.axisId]) { - axisState_[axis.axisId] = axisState; - if (axisState != 0) { - for (size_t i = 0; i < results.size(); i++) { - if (!IsAnalogStickKey(results[i])) - pspKey(results[i], KEY_DOWN); - } - // Also unpress the other direction (unless both directions press the same key.) - for (size_t i = 0; i < resultsOpposite.size(); i++) { - if (!IsAnalogStickKey(resultsOpposite[i]) && std::find(results.begin(), results.end(), resultsOpposite[i]) == results.end()) - pspKey(resultsOpposite[i], KEY_UP); - } - } else if (axisState == 0) { - // Release both directions, trying to deal with some erratic controllers that can cause it to stick. - for (size_t i = 0; i < results.size(); i++) { - if (!IsAnalogStickKey(results[i])) - pspKey(results[i], KEY_UP); - } - for (size_t i = 0; i < resultsOpposite.size(); i++) { - if (!IsAnalogStickKey(resultsOpposite[i])) - pspKey(resultsOpposite[i], KEY_UP); - } - } - } + return controlMapper_.Axis(axis); } class GameInfoBGView : public UI::InertView { diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index df2adf9d48..12d2baaf49 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -27,6 +27,7 @@ #include "Common/UI/UIScreen.h" #include "Common/UI/Tween.h" #include "Core/KeyMap.h" +#include "Core/ControlMapper.h" struct AxisInput; @@ -62,43 +63,34 @@ private: void bootComplete(); bool hasVisibleUI(); void renderUI(); - void processAxis(const AxisInput &axis, int direction); - void pspKey(int pspKeyCode, int flags); void onVKeyDown(int virtualKeyCode); void onVKeyUp(int virtualKeyCode); - void setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero = true); void autoLoad(); void checkPowerDown(); UI::Event OnDevMenu; UI::Event OnChatMenu; - bool bootPending_; + bool bootPending_ = true; Path gamePath_; // Something invalid was loaded, don't try to emulate - bool invalid_; - bool quit_; + bool invalid_ = true; + bool quit_ = false; bool stopRender_ = false; std::string errorMessage_; // If set, pauses at the end of the frame. - bool pauseTrigger_; - - // To track mappable virtual keys. We can have as many as we want. - bool virtKeys[VIRTKEY_COUNT]; + bool pauseTrigger_ = false; // In-memory save state used for freezeFrame, which is useful for debugging. std::vector freezeState_; std::string tag_; - // De-noise mapped axis updates - int axisState_[JOYSTICK_AXIS_MAX]; - - double saveStatePreviewShownTime_; - AsyncImageFileView *saveStatePreview_; + double saveStatePreviewShownTime_ = 0.0; + AsyncImageFileView *saveStatePreview_ = nullptr; int saveStateSlot_; UI::CallbackColorTween *loadingViewColor_ = nullptr; @@ -113,4 +105,6 @@ private: bool autoRotatingAnalogCW_ = false; bool autoRotatingAnalogCCW_ = false; + + ControlMapper controlMapper_; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index a539c38ca1..cf363b3a08 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -78,6 +78,7 @@ #include "Common/OSVersion.h" #include "Common/GPU/ShaderTranslation.h" +#include "Core/ControlMapper.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/Core.h" @@ -1326,35 +1327,6 @@ bool NativeKey(const KeyInput &key) { return retval; } -static float MapAxisValue(float v) { - const float deadzone = g_Config.fAnalogDeadzone; - const float invDeadzone = g_Config.fAnalogInverseDeadzone; - const float sensitivity = g_Config.fAnalogSensitivity; - const float sign = v >= 0.0f ? 1.0f : -1.0f; - return sign * Clamp(invDeadzone + (abs(v) - deadzone) / (1.0f - deadzone) * (sensitivity - invDeadzone), 0.0f, 1.0f); -} - -static void ConvertAnalogStick(float &x, float &y) { - const bool isCircular = g_Config.bAnalogIsCircular; - - float norm = std::max(fabsf(x), fabsf(y)); - - if (norm == 0.0f) - return; - - if (isCircular) { - float newNorm = sqrtf(x * x + y * y); - float factor = newNorm / norm; - x *= factor; - y *= factor; - norm = newNorm; - } - - float mappedNorm = MapAxisValue(norm); - x = Clamp(x / norm * mappedNorm, -1.0f, 1.0f); - y = Clamp(y / norm * mappedNorm, -1.0f, 1.0f); -} - static bool AnalogStickAxis(const AxisInput &axis) { static float history[JOYSTICK_AXIS_MAX+1] = { 0.0f }; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index f3f026c4d6..dd271d6221 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -382,6 +382,7 @@ + @@ -616,6 +617,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 89b790f3c4..e59d84f7b3 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -1021,9 +1021,6 @@ Ext\libzip - - Ext\libzip - Ext\libzip @@ -1110,6 +1107,7 @@ + @@ -1748,6 +1746,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 4b45085687..62df2e1a3f 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -378,6 +378,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HW/SasAudio.cpp.arm \ $(SRC)/Core/HW/SasReverb.cpp.arm \ $(SRC)/Core/HW/StereoResampler.cpp.arm \ + $(SRC)/Core/ControlMapper.cpp \ $(SRC)/Core/Core.cpp \ $(SRC)/Core/Compatibility.cpp \ $(SRC)/Core/Config.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index d4e45f7803..77fd240274 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -427,6 +427,7 @@ SOURCES_CXX += \ $(EXTDIR)/jpge/jpge.cpp \ $(COREDIR)/AVIDump.cpp \ $(COREDIR)/Config.cpp \ + $(COREDIR)/ControlMapper.cpp \ $(COREDIR)/TextureReplacer.cpp \ $(COREDIR)/Core.cpp \ $(COREDIR)/WaveFile.cpp \