From f178d1bd85223a0a1638f183f5a9af6e00a09efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 Mar 2023 11:31:45 +0200 Subject: [PATCH 1/9] Add an even cheaper version of TinySet called FixedTinyVec that's, well, fixed. --- Common/Data/Collections/TinySet.h | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Common/Data/Collections/TinySet.h b/Common/Data/Collections/TinySet.h index 4daf8279a3..702a98dc0f 100644 --- a/Common/Data/Collections/TinySet.h +++ b/Common/Data/Collections/TinySet.h @@ -134,3 +134,68 @@ private: T fastLookup_[MaxFastSize]; std::vector *slowLookup_ = nullptr; }; + +template +struct FixedTinyVec { + ~FixedTinyVec() {} + // WARNING: Can fail if you exceed MaxSize! + inline bool push_back(const T &t) { + if (count_ < MaxSize) { + data_[count_++] = t; + return true; + } else { + return false; + } + } + // WARNING: Can fail if you exceed MaxSize! + inline T *add_back() { + if (count_ < MaxSize) { + return &data_[count_++]; + } + return nullptr; + } + // Invalid if empty(). + void pop_back() { count_--; } + + // Unlike TinySet, we can trivially support begin/end as pointers. + T *begin() { return data_; } + T *end() { return data_ + count_; } + const T *begin() const { return data_; } + const T *end() const { return data_ + count_; } + + size_t capacity() const { return MaxSize; } + void clear() { count_ = 0; } + bool empty() const { return count_ == 0; } + size_t size() const { return count_; } + + bool contains(T t) const { + for (int i = 0; i < count_; i++) { + if (data_[i] == t) + return true; + } + return false; + } + + // Out of bounds (past size() - 1) is undefined behavior. + T &operator[] (const size_t index) { return data_[index]; } + const T &operator[] (const size_t index) const { return data_[index]; } + + // These two are invalid if empty(). + const T &back() const { return (*this)[size() - 1]; } + const T &front() const { return (*this)[0]; } + + bool operator == (const FixedTinyVec &other) const { + if (count_ != other.count_) + return false; + for (size_t i = 0; i < count_; i++) { + if (!(data_[i] == other.data_[i])) { + return false; + } + } + return true; + } + +private: + int count_ = 0; // first in the struct just so it's more visible in the VS debugger. + T data_[MaxSize]; +}; From 0e1c42ce7063489e7b0ce035324e8663386e3400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 00:12:14 +0200 Subject: [PATCH 2/9] Plumb multimappings all the way through. --- Common/Input/InputState.h | 9 +++++ Core/ControlMapper.cpp | 55 +++++++++++++++++++--------- Core/KeyMap.cpp | 72 ++++++++++++++++++++++++------------- Core/KeyMap.h | 53 +++++++++++++++++++++++---- Core/KeyMapDefaults.cpp | 4 +-- UI/ControlMappingScreen.cpp | 30 +++++++++------- UI/ControlMappingScreen.h | 10 +++--- Windows/DinputDevice.cpp | 2 -- 8 files changed, 167 insertions(+), 68 deletions(-) diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h index b1e30acefa..305939faae 100644 --- a/Common/Input/InputState.h +++ b/Common/Input/InputState.h @@ -131,6 +131,15 @@ public: if (keyCode < other.keyCode) return true; return false; } + // Needed for composition. + bool operator > (const InputMapping &other) const { + if (deviceId > other.deviceId) return true; + if (deviceId < other.deviceId) return false; + if (keyCode > other.keyCode) return true; + return false; + } + + // This one is iffy with the != ANY checks. Should probably be a named method. bool operator == (const InputMapping &other) const { if (deviceId != other.deviceId && deviceId != DEVICE_ID_ANY && other.deviceId != DEVICE_ID_ANY) return false; if (keyCode != other.keyCode) return false; diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index 60b071d880..64c2f3552c 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -13,6 +13,8 @@ #include "Core/CoreParameter.h" #include "Core/System.h" +using KeyMap::MultiInputMapping; + // TODO: Possibly make these thresholds configurable? static float GetDeviceAxisThreshold(int device) { return device == DEVICE_ID_MOUSE ? AXIS_BIND_THRESHOLD_MOUSE : AXIS_BIND_THRESHOLD; @@ -219,19 +221,27 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { mappingBit = RotatePSPKeyCode(mappingBit); } - std::vector inputMappings; + std::vector inputMappings; if (!KeyMap::InputMappingsFromPspButton(mappingBit, &inputMappings, false)) continue; // If a mapping could consist of a combo, we could trivially check it here. - for (auto &mapping : inputMappings) { + for (auto &multiMapping : inputMappings) { + if (multiMapping.empty()) + continue; // Check if the changed mapping was involved in this PSP key. - if (changedMapping == mapping) { + if (multiMapping.mappings.contains(changedMapping)) { changedButtonMask |= mask; } - - auto iter = curInput_.find(mapping); - if (iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId)) { + // Check if all inputs are "on". + bool all = true; + for (auto mapping : multiMapping.mappings) { + auto iter = curInput_.find(mapping); + bool down = iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId); + if (!down) + all = false; + } + if (all) { buttonMask |= mask; } } @@ -243,7 +253,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { // OK, handle all the virtual keys next. For these we need to do deltas here and send events. for (int i = 0; i < VIRTKEY_COUNT; i++) { int vkId = i + VIRTKEY_FIRST; - std::vector inputMappings; + std::vector inputMappings; if (!KeyMap::InputMappingsFromPspButton(vkId, &inputMappings, false)) continue; @@ -253,20 +263,27 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { float threshold = 1.0f; bool touchedByMapping = false; float value = 0.0f; - for (auto &mapping : inputMappings) { - if (mapping == changedMapping) { + for (auto &multiMapping : inputMappings) { + if (multiMapping.mappings.contains(changedMapping)) { touchedByMapping = true; } - auto iter = curInput_.find(mapping); - if (iter != curInput_.end()) { - if (mapping.IsAxis()) { - threshold = GetDeviceAxisThreshold(iter->first.deviceId); - value += MapAxisValue(iter->second, vkId, mapping, changedMapping, &touchedByMapping); + float product = 1.0f; // We multiply the various inputs in a combo mapping with each other. + for (auto mapping : multiMapping.mappings) { + auto iter = curInput_.find(mapping); + if (iter != curInput_.end()) { + if (mapping.IsAxis()) { + threshold = GetDeviceAxisThreshold(iter->first.deviceId); + product *= MapAxisValue(iter->second, vkId, mapping, changedMapping, &touchedByMapping); + } else { + product *= iter->second; + } } else { - value += iter->second; + product = 0.0f; } } + + value += product; } if (!touchedByMapping) { @@ -280,8 +297,12 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { // that still works, though a bit weaker. We could also zero here, but you never know who relies on such strange tricks.. // Note: This is an old problem, it didn't appear with the refactoring. if (!changedMapping.IsAxis()) { - for (auto &mapping : inputMappings) { - if (mapping.IsAxis()) { + for (auto &multiMapping : inputMappings) { + bool anyAxis = false; + for (auto &mapping : multiMapping.mappings) { + if (mapping.IsAxis()) { + anyAxis = true; + } curInput_[mapping] = ReduceMagnitude(curInput_[mapping]); } } diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index 52f907ed8e..994ddb9e60 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -45,6 +45,17 @@ std::set g_seenDeviceIds; bool g_swapDpadWithLStick = false; +// Utility... +void SingleInputMappingFromPspButton(int btn, std::vector *mappings, bool ignoreMouse) { + std::vector multiMappings; + InputMappingsFromPspButton(btn, &multiMappings, ignoreMouse); + mappings->clear(); + for (auto &mapping : multiMappings) { + _dbg_assert_(!mapping.empty()); + mappings->push_back(mapping.mappings[0]); + } +} + // TODO: This is such a mess... void UpdateNativeMenuKeys() { std::vector confirmKeys, cancelKeys; @@ -55,14 +66,14 @@ void UpdateNativeMenuKeys() { int cancelKey = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? CTRL_CIRCLE : CTRL_CROSS; // Mouse mapping might be problematic in UI, so let's ignore mouse for UI - InputMappingsFromPspButton(confirmKey, &confirmKeys, true); - InputMappingsFromPspButton(cancelKey, &cancelKeys, true); - InputMappingsFromPspButton(CTRL_LTRIGGER, &tabLeft, true); - InputMappingsFromPspButton(CTRL_RTRIGGER, &tabRight, true); - InputMappingsFromPspButton(CTRL_UP, &upKeys, true); - InputMappingsFromPspButton(CTRL_DOWN, &downKeys, true); - InputMappingsFromPspButton(CTRL_LEFT, &leftKeys, true); - InputMappingsFromPspButton(CTRL_RIGHT, &rightKeys, true); + SingleInputMappingFromPspButton(confirmKey, &confirmKeys, true); + SingleInputMappingFromPspButton(cancelKey, &cancelKeys, true); + SingleInputMappingFromPspButton(CTRL_LTRIGGER, &tabLeft, true); + SingleInputMappingFromPspButton(CTRL_RTRIGGER, &tabRight, true); + SingleInputMappingFromPspButton(CTRL_UP, &upKeys, true); + SingleInputMappingFromPspButton(CTRL_DOWN, &downKeys, true); + SingleInputMappingFromPspButton(CTRL_LEFT, &leftKeys, true); + SingleInputMappingFromPspButton(CTRL_RIGHT, &rightKeys, true); #ifdef __ANDROID__ // Hardcode DPAD on Android @@ -493,7 +504,7 @@ bool InputMappingToPspButton(const InputMapping &mapping, std::vector *pspB bool found = false; for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) { for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) { - if (*iter2 == mapping) { + if (iter2->EqualsSingleMapping(mapping)) { if (pspButtons) pspButtons->push_back(CheckAxisSwap(iter->first)); found = true; @@ -503,12 +514,12 @@ bool InputMappingToPspButton(const InputMapping &mapping, std::vector *pspB return found; } -bool InputMappingsFromPspButton(int btn, std::vector *mappings, bool ignoreMouse) { +bool InputMappingsFromPspButton(int btn, std::vector *mappings, bool ignoreMouse) { bool mapped = false; for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) { if (iter->first == btn) { for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) { - if (mappings && (!ignoreMouse || iter2->deviceId != DEVICE_ID_MOUSE)) { + if (mappings && (!ignoreMouse || iter2->HasMouse())) { mapped = true; mappings->push_back(*iter2); } @@ -525,8 +536,11 @@ MappedAnalogAxes MappedAxesForDevice(int deviceId) { auto findAxisId = [&](int btn) -> MappedAnalogAxis { MappedAnalogAxis info{ -1 }; for (const auto &key : g_controllerMap[btn]) { - if (key.deviceId == deviceId) { - info.axisId = TranslateKeyCodeToAxis(key.keyCode, &info.direction); + // Only consider single mappings, combos don't make much sense for these. + if (key.mappings.empty()) continue; + auto &mapping = key.mappings[0]; + if (mapping.deviceId == deviceId) { + info.axisId = TranslateKeyCodeToAxis(mapping.keyCode, &info.direction); return info; } } @@ -562,7 +576,7 @@ void RemoveButtonMapping(int btn) { bool IsKeyMapped(int device, int key) { for (auto &iter : g_controllerMap) { for (auto &mappedKey : iter.second) { - if (mappedKey == InputMapping(device, key)) { + if (mappedKey.mappings.contains(InputMapping(device, key))) { return true; } } @@ -570,7 +584,7 @@ bool IsKeyMapped(int device, int key) { return false; } -bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key) { +bool ReplaceSingleKeyMapping(int btn, int index, MultiInputMapping key) { // Check for duplicate for (int i = 0; i < (int)g_controllerMap[btn].size(); ++i) { if (i != index && g_controllerMap[btn][i] == key) { @@ -584,14 +598,18 @@ bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key) { KeyMap::g_controllerMap[btn][index] = key; g_controllerMapGeneration++; - g_seenDeviceIds.insert(key.deviceId); + for (auto &mapping : key.mappings) { + g_seenDeviceIds.insert(mapping.deviceId); + } UpdateNativeMenuKeys(); return true; } -void SetInputMapping(int btn, const InputMapping &key, bool replace) { - if (key.keyCode < 0) +void SetInputMapping(int btn, const MultiInputMapping &key, bool replace) { + if (key.empty()) { + g_controllerMap.erase(btn); return; + } if (replace) { RemoveButtonMapping(btn); g_controllerMap[btn].clear(); @@ -605,7 +623,9 @@ void SetInputMapping(int btn, const InputMapping &key, bool replace) { } g_controllerMapGeneration++; - g_seenDeviceIds.insert(key.deviceId); + for (auto &mapping : key.mappings) { + g_seenDeviceIds.insert(mapping.deviceId); + } UpdateNativeMenuKeys(); } @@ -667,12 +687,14 @@ void LoadFromIni(IniFile &file) { SplitString(value, ',', mappings); for (size_t j = 0; j < mappings.size(); j++) { + // TODO: Properly parse multiple mappings. + std::vector parts; SplitString(mappings[j], '-', parts); int deviceId = atoi(parts[0].c_str()); int keyCode = atoi(parts[1].c_str()); - SetInputMapping(psp_button_names[i].key, InputMapping(deviceId, keyCode), false); + SetInputMapping(psp_button_names[i].key, MultiInputMapping(InputMapping(deviceId, keyCode)), false); g_seenDeviceIds.insert(deviceId); } } @@ -684,13 +706,15 @@ void SaveToIni(IniFile &file) { Section *controls = file.GetOrCreateSection("ControlMapping"); for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) { - std::vector keys; + std::vector keys; InputMappingsFromPspButton(psp_button_names[i].key, &keys, false); std::string value; for (size_t j = 0; j < keys.size(); j++) { + auto mapping = keys[j].mappings[0]; + char temp[128]; - sprintf(temp, "%i-%i", keys[j].deviceId, keys[j].keyCode); + sprintf(temp, "%i-%i", mapping.deviceId, mapping.keyCode); value += temp; if (j != keys.size() - 1) value += ","; @@ -756,8 +780,8 @@ void AutoConfForPad(const std::string &name) { #endif // Add a couple of convenient keyboard mappings by default, too. - g_controllerMap[VIRTKEY_PAUSE].push_back(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE)); - g_controllerMap[VIRTKEY_FASTFORWARD].push_back(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_TAB)); + g_controllerMap[VIRTKEY_PAUSE].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE))); + g_controllerMap[VIRTKEY_FASTFORWARD].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_TAB))); g_controllerMapGeneration++; } diff --git a/Core/KeyMap.h b/Core/KeyMap.h index 113117173b..3153cfe255 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -24,6 +24,7 @@ #include "Common/Input/InputState.h" // InputMapping #include "Common/Input/KeyCodes.h" // keyboard keys +#include "Common/Data/Collections/TinySet.h" #include "Core/KeyMapDefaults.h" #define KEYMAP_ERROR_KEY_ALREADY_USED -1 @@ -77,8 +78,6 @@ enum { const float AXIS_BIND_THRESHOLD = 0.75f; const float AXIS_BIND_THRESHOLD_MOUSE = 0.01f; -typedef std::map> KeyMapping; - struct MappedAnalogAxis { int axisId; int direction; @@ -104,10 +103,50 @@ struct MappedAnalogAxes { class IniFile; namespace KeyMap { + // Combo of InputMappings. + struct MultiInputMapping { + MultiInputMapping() {} + explicit MultiInputMapping(const InputMapping &mapping) { + mappings.push_back(mapping); + } + bool operator <(const MultiInputMapping &other) { + for (size_t i = 0; i < mappings.capacity(); i++) { + // If one ran out of entries, the other wins. + if (mappings.size() == i && other.mappings.size() > i) return true; + if (mappings.size() >= i && other.mappings.size() == i) return false; + if (mappings[i] < other.mappings[i]) return true; + if (mappings[i] > other.mappings[i]) return false; + } + return false; + } + + bool operator ==(const MultiInputMapping &other) const { + return mappings == other.mappings; + } + + bool EqualsSingleMapping(const InputMapping &other) const { + return mappings.size() == 1 && mappings[0] == other; + } + + bool empty() const { + return mappings.empty(); + } + + bool HasMouse() const { + for (auto &m : mappings) { + return m.deviceId == DEVICE_ID_MOUSE; + } + return false; + } + + FixedTinyVec mappings; + }; + + typedef std::map> KeyMapping; + extern KeyMapping g_controllerMap; extern std::set g_seenDeviceIds; extern int g_controllerMapGeneration; - // Key & Button names struct KeyMap_IntStrPair { int key; @@ -126,14 +165,16 @@ namespace KeyMap { // Use to translate input mappings to and from PSP buttons. You should have already translated // your platform's keys to InputMapping keys. + + // Note that this one does not handle combos, since there's only one input. bool InputMappingToPspButton(const InputMapping &mapping, std::vector *pspButtons); - bool InputMappingsFromPspButton(int btn, std::vector *keys, bool ignoreMouse); + bool InputMappingsFromPspButton(int btn, std::vector *keys, bool ignoreMouse); // Configure the key or axis mapping. // Any configuration will be saved to the Core config. - void SetInputMapping(int psp_key, const InputMapping &key, bool replace); + void SetInputMapping(int psp_key, const MultiInputMapping &key, bool replace); // Return false if bind was a duplicate and got removed - bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key); + bool ReplaceSingleKeyMapping(int btn, int index, MultiInputMapping key); MappedAnalogAxes MappedAxesForDevice(int deviceId); diff --git a/Core/KeyMapDefaults.cpp b/Core/KeyMapDefaults.cpp index 3ffa066f85..63c4294305 100644 --- a/Core/KeyMapDefaults.cpp +++ b/Core/KeyMapDefaults.cpp @@ -341,9 +341,9 @@ static const DefMappingStruct defaultVRRightController[] = { static void SetDefaultKeyMap(int deviceId, const DefMappingStruct *array, size_t count, bool replace) { for (size_t i = 0; i < count; i++) { if (array[i].direction == 0) - SetInputMapping(array[i].pspKey, InputMapping(deviceId, array[i].keyOrAxis), replace); + SetInputMapping(array[i].pspKey, MultiInputMapping(InputMapping(deviceId, array[i].keyOrAxis)), replace); else - SetInputMapping(array[i].pspKey, InputMapping(deviceId, array[i].keyOrAxis, array[i].direction), replace); + SetInputMapping(array[i].pspKey, MultiInputMapping(InputMapping(deviceId, array[i].keyOrAxis, array[i].direction)), replace); } g_seenDeviceIds.insert(deviceId); } diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 7d267c2b60..c00efe3ec0 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -51,6 +51,8 @@ #include "android/jni/app-android.h" #endif +using KeyMap::MultiInputMapping; + class SingleControlMapper : public UI::LinearLayout { public: SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr); @@ -66,7 +68,7 @@ private: UI::EventReturn OnReplace(UI::EventParams ¶ms); UI::EventReturn OnReplaceAll(UI::EventParams ¶ms); - void MappedCallback(InputMapping key); + void MappedCallback(MultiInputMapping key); enum Action { NONE, @@ -134,13 +136,17 @@ void SingleControlMapper::Refresh() { LinearLayout *rightColumn = root->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(rightColumnWidth, WRAP_CONTENT))); rightColumn->SetSpacing(2.0f); - std::vector mappings; + std::vector mappings; KeyMap::InputMappingsFromPspButton(pspKey_, &mappings, false); rows_.clear(); for (size_t i = 0; i < mappings.size(); i++) { - std::string deviceName = GetDeviceName(mappings[i].deviceId); - std::string keyName = KeyMap::GetKeyOrAxisName(mappings[i]); + // TODO: Correctly handle multis. + + auto &mapping = mappings[i].mappings[0]; + + std::string deviceName = GetDeviceName(mapping.deviceId); + std::string keyName = KeyMap::GetKeyOrAxisName(mapping); LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); row->SetSpacing(2.0f); @@ -162,7 +168,7 @@ void SingleControlMapper::Refresh() { } } -void SingleControlMapper::MappedCallback(InputMapping kdf) { +void SingleControlMapper::MappedCallback(MultiInputMapping kdf) { switch (action_) { case ADD: KeyMap::SetInputMapping(pspKey_, kdf, false); @@ -346,7 +352,7 @@ bool KeyMappingNewKeyDialog::key(const KeyInput &key) { return true; mapped_ = true; - InputMapping kdf(key.deviceId, key.keyCode); + MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode)); TriggerFinish(DR_YES); if (callback_ && pspBtn_ != VIRTKEY_SPEED_ANALOG) callback_(kdf); @@ -378,7 +384,7 @@ bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) { } mapped_ = true; - InputMapping kdf(key.deviceId, key.keyCode); + MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode)); TriggerFinish(DR_YES); g_Config.bMapMouse = false; if (callback_) @@ -409,7 +415,7 @@ void KeyMappingNewKeyDialog::axis(const AxisInput &axis) { if (axis.value > AXIS_BIND_THRESHOLD) { mapped_ = true; - InputMapping kdf(axis.deviceId, axis.axisId, 1); + MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1)); TriggerFinish(DR_YES); if (callback_) callback_(kdf); @@ -417,7 +423,7 @@ void KeyMappingNewKeyDialog::axis(const AxisInput &axis) { if (axis.value < -AXIS_BIND_THRESHOLD) { mapped_ = true; - InputMapping kdf(axis.deviceId, axis.axisId, -1); + MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1)); TriggerFinish(DR_YES); if (callback_) callback_(kdf); @@ -432,7 +438,7 @@ void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) { if (axis.value > AXIS_BIND_THRESHOLD) { mapped_ = true; - InputMapping kdf(axis.deviceId, axis.axisId, 1); + MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1)); TriggerFinish(DR_YES); if (callback_) callback_(kdf); @@ -440,7 +446,7 @@ void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) { if (axis.value < -AXIS_BIND_THRESHOLD) { mapped_ = true; - InputMapping kdf(axis.deviceId, axis.axisId, -1); + MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1)); TriggerFinish(DR_YES); if (callback_) callback_(kdf); @@ -1147,7 +1153,7 @@ UI::EventReturn VisualMappingScreen::OnBindAll(UI::EventParams &e) { return UI::EVENT_DONE; } -void VisualMappingScreen::HandleKeyMapping(InputMapping key) { +void VisualMappingScreen::HandleKeyMapping(MultiInputMapping key) { KeyMap::SetInputMapping(nextKey_, key, replace_); if (bindAll_ < 0) { diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index 98df0c6b56..d02eef8e88 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -56,7 +56,7 @@ private: class KeyMappingNewKeyDialog : public PopupScreen { public: - explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) + explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) : PopupScreen(i18n->T("Map Key"), "Cancel", ""), pspBtn_(btn), callback_(callback) {} const char *tag() const override { return "KeyMappingNewKey"; } @@ -75,14 +75,14 @@ protected: private: int pspBtn_; - std::function callback_; + std::function callback_; bool mapped_ = false; // Prevent double registrations double delayUntil_ = 0.0f; }; class KeyMappingNewMouseKeyDialog : public PopupScreen { public: - KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) + KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) : PopupScreen(i18n->T("Map Mouse"), "", ""), pspBtn_(btn), callback_(callback), mapped_(false) {} const char *tag() const override { return "KeyMappingNewMouseKey"; } @@ -99,7 +99,7 @@ protected: private: int pspBtn_; - std::function callback_; + std::function callback_; bool mapped_; // Prevent double registrations }; @@ -189,7 +189,7 @@ protected: private: UI::EventReturn OnMapButton(UI::EventParams &e); UI::EventReturn OnBindAll(UI::EventParams &e); - void HandleKeyMapping(InputMapping key); + void HandleKeyMapping(KeyMap::MultiInputMapping key); void MapNext(bool successive); MockPSP *psp_ = nullptr; diff --git a/Windows/DinputDevice.cpp b/Windows/DinputDevice.cpp index 2bdde3c89c..43a2340f27 100644 --- a/Windows/DinputDevice.cpp +++ b/Windows/DinputDevice.cpp @@ -244,8 +244,6 @@ int DinputDevice::UpdateState() { AxisInput axis; axis.deviceId = DEVICE_ID_PAD_0 + pDevNum; - auto axesToSquare = KeyMap::MappedAxesForDevice(axis.deviceId); - SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X); SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y); SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z); From 49a2c93b66d2670656ef0267897061ffad5dc3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 13:43:42 +0200 Subject: [PATCH 3/9] Make multi-mapping save-able, add test to verify backwards and forwards compat --- Common/Input/InputState.cpp | 18 ++++++++++++++++ Common/Input/InputState.h | 5 ++++- Core/KeyMap.cpp | 38 +++++++++++++++++++++----------- Core/KeyMap.h | 4 ++++ unittest/UnitTest.cpp | 43 +++++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/Common/Input/InputState.cpp b/Common/Input/InputState.cpp index a788ed10a8..3db6dc137c 100644 --- a/Common/Input/InputState.cpp +++ b/Common/Input/InputState.cpp @@ -3,6 +3,7 @@ #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" +#include "Common/StringUtils.h" const char *GetDeviceName(int deviceId) { switch (deviceId) { @@ -79,6 +80,23 @@ int GetAnalogYDirection(int deviceId) { return 0; } +// NOTE: Changing the format of FromConfigString/ToConfigString breaks controls.ini backwards compatibility. +InputMapping InputMapping::FromConfigString(const std::string &str) { + std::vector parts; + SplitString(str, '-', parts); + int deviceId = atoi(parts[0].c_str()); + int keyCode = atoi(parts[1].c_str()); + + InputMapping mapping; + mapping.deviceId = deviceId; + mapping.keyCode = keyCode; + return mapping; +} + +std::string InputMapping::ToConfigString() const { + return StringFromFormat("%d-%d", deviceId, keyCode); +} + void InputMapping::FormatDebug(char *buffer, size_t bufSize) const { if (IsAxis()) { int direction; diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h index 305939faae..73abefca31 100644 --- a/Common/Input/InputState.h +++ b/Common/Input/InputState.h @@ -5,8 +5,8 @@ #include #include +#include -#include "Common/Math/lin/vec3.h" #include "Common/Input/KeyCodes.h" #include "Common/Log.h" @@ -105,6 +105,9 @@ public: _dbg_assert_(direction != 0); } + static InputMapping FromConfigString(const std::string &str); + std::string ToConfigString() const; + int deviceId; int keyCode; // Can also represent an axis with direction, if encoded properly. diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index 994ddb9e60..01ecb9cffe 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -687,15 +687,12 @@ void LoadFromIni(IniFile &file) { SplitString(value, ',', mappings); for (size_t j = 0; j < mappings.size(); j++) { - // TODO: Properly parse multiple mappings. + MultiInputMapping input = MultiInputMapping::FromConfigString(mappings[j]); + SetInputMapping(psp_button_names[i].key, input, false); - std::vector parts; - SplitString(mappings[j], '-', parts); - int deviceId = atoi(parts[0].c_str()); - int keyCode = atoi(parts[1].c_str()); - - SetInputMapping(psp_button_names[i].key, MultiInputMapping(InputMapping(deviceId, keyCode)), false); - g_seenDeviceIds.insert(deviceId); + for (auto mapping : input.mappings) { + g_seenDeviceIds.insert(mapping.deviceId); + } } } @@ -711,11 +708,7 @@ void SaveToIni(IniFile &file) { std::string value; for (size_t j = 0; j < keys.size(); j++) { - auto mapping = keys[j].mappings[0]; - - char temp[128]; - sprintf(temp, "%i-%i", mapping.deviceId, mapping.keyCode); - value += temp; + value += keys[j].ToConfigString(); if (j != keys.size() - 1) value += ","; } @@ -858,4 +851,23 @@ const char *GetVirtKeyName(int vkey) { return g_vKeyNames[index]; } +MultiInputMapping MultiInputMapping::FromConfigString(const std::string &str) { + MultiInputMapping out; + std::vector parts; + SplitString(str, ':', parts); + for (auto iter : parts) { + out.mappings.push_back(InputMapping::FromConfigString(iter)); + } + return out; +} + +std::string MultiInputMapping::ToConfigString() const { + std::string out; + for (auto iter : mappings) { + out += iter.ToConfigString() + ":"; + } + out.pop_back(); // remove the last ':' + return out; +} + } // KeyMap diff --git a/Core/KeyMap.h b/Core/KeyMap.h index 3153cfe255..f638e53806 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -109,6 +109,10 @@ namespace KeyMap { explicit MultiInputMapping(const InputMapping &mapping) { mappings.push_back(mapping); } + + static MultiInputMapping FromConfigString(const std::string &str); + std::string ToConfigString() const; + bool operator <(const MultiInputMapping &other) { for (size_t i = 0; i < mappings.capacity(); i++) { // If one ran out of entries, the other wins. diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 10f09e872e..bcbb7ad4e0 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -62,6 +62,7 @@ #include "Common/File/VFS/DirectoryReader.h" #include "Core/FileSystems/ISOFileSystem.h" #include "Core/MemMap.h" +#include "Core/KeyMap.h" #include "Core/MIPS/MIPSVFPUUtils.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Common/GPUStateUtils.h" @@ -849,6 +850,47 @@ static bool TestDepthMath() { return true; } +bool TestInputMapping() { + InputMapping mapping; + mapping.deviceId = 10; + mapping.keyCode = 20; + InputMapping mapping2; + mapping2.deviceId = 18; + mapping2.keyCode = 38; + std::string cfg = mapping.ToConfigString(); + + InputMapping parsedMapping = InputMapping::FromConfigString(cfg); + EXPECT_EQ_INT(parsedMapping.deviceId, mapping.deviceId); + EXPECT_EQ_INT(parsedMapping.keyCode, mapping.keyCode); + + using KeyMap::MultiInputMapping; + MultiInputMapping multi(mapping); + + EXPECT_EQ_STR(multi.ToConfigString(), mapping.ToConfigString()); + + multi.mappings.push_back(mapping2); + EXPECT_FALSE(multi.EqualsSingleMapping(mapping)); + EXPECT_TRUE(multi.mappings.contains(mapping2)); + EXPECT_TRUE(multi.mappings.contains(mapping)); + + std::string cfgMulti = multi.ToConfigString(); + + EXPECT_EQ_STR(cfgMulti, std::string("10-20:18-38")); + + MultiInputMapping parsedMulti = MultiInputMapping::FromConfigString(cfgMulti); + + EXPECT_EQ_INT((int)parsedMulti.mappings.size(), 2); + + // OK, both single and multiple mappings parse. Let's now see if the old parsing can handle a multimapping. + // This is a requirement for the new format. + + InputMapping parsedMultiSingle = InputMapping::FromConfigString(cfgMulti); // yes this is an intentional mismatch + // We should get the first mapping. + EXPECT_TRUE(parsedMultiSingle == mapping); + return true; +} + + typedef bool (*TestFunc)(); struct TestItem { const char *name; @@ -901,6 +943,7 @@ TestItem availableTests[] = { TEST_ITEM(TinySet), TEST_ITEM(SmallDataConvert), TEST_ITEM(DepthMath), + TEST_ITEM(InputMapping), }; int main(int argc, const char *argv[]) { From 8c9fee064e554b6131bb44b2babaaffbabe2bbad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 15:00:22 +0200 Subject: [PATCH 4/9] Display multi-mappings correctly in the mapping list --- Core/KeyMap.cpp | 14 ++++++++++++++ Core/KeyMap.h | 2 ++ UI/ControlMappingScreen.cpp | 10 ++-------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index 01ecb9cffe..694402eb9a 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -870,4 +870,18 @@ std::string MultiInputMapping::ToConfigString() const { return out; } +std::string MultiInputMapping::ToVisualString() const { + std::string out; + for (auto iter : mappings) { + out += std::string(GetDeviceName(iter.deviceId)) + "." + GetKeyOrAxisName(iter) + " + "; + } + if (!out.empty()) { + // remove the last ' + ' + out.pop_back(); + out.pop_back(); + out.pop_back(); + } + return out; +} + } // KeyMap diff --git a/Core/KeyMap.h b/Core/KeyMap.h index f638e53806..f8f9aa2f57 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -113,6 +113,8 @@ namespace KeyMap { static MultiInputMapping FromConfigString(const std::string &str); std::string ToConfigString() const; + std::string ToVisualString() const; + bool operator <(const MultiInputMapping &other) { for (size_t i = 0; i < mappings.capacity(); i++) { // If one ran out of entries, the other wins. diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index c00efe3ec0..2113cc8079 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -141,18 +141,12 @@ void SingleControlMapper::Refresh() { rows_.clear(); for (size_t i = 0; i < mappings.size(); i++) { - // TODO: Correctly handle multis. - - auto &mapping = mappings[i].mappings[0]; - - std::string deviceName = GetDeviceName(mapping.deviceId); - std::string keyName = KeyMap::GetKeyOrAxisName(mapping); - + std::string multiMappingString = mappings[i].ToVisualString(); LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); row->SetSpacing(2.0f); rows_.push_back(row); - Choice *c = row->Add(new Choice(deviceName + "." + keyName, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f))); + Choice *c = row->Add(new Choice(multiMappingString, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f))); c->SetTag(StringFromFormat("%d_Change%d", (int)i, pspKey_)); c->OnClick.Handle(this, &SingleControlMapper::OnReplace); From 88e73801fe10dd73c53838f8df3d66e9f89afc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 17:50:17 +0200 Subject: [PATCH 5/9] Fix the binding UI to support multi-bind --- Core/KeyMap.h | 1 + UI/ControlMappingScreen.cpp | 76 ++++++++++++++++++++++++------------- UI/ControlMappingScreen.h | 8 +++- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/Core/KeyMap.h b/Core/KeyMap.h index f8f9aa2f57..b88c0e675e 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -76,6 +76,7 @@ enum { }; const float AXIS_BIND_THRESHOLD = 0.75f; +const float AXIS_BIND_RELEASE_THRESHOLD = 0.35f; // Used during mapping only to detect a "key-up" reliably. const float AXIS_BIND_THRESHOLD_MOUSE = 0.01f; struct MappedAnalogAxis { diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 2113cc8079..b6d4424172 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -111,7 +111,7 @@ void SingleControlMapper::Refresh() { float itemH = 55.0f; float leftColumnWidth = 200; - float rightColumnWidth = 250; // TODO: Should be flexible somehow. Maybe we need to implement Measure. + float rightColumnWidth = 350; // TODO: Should be flexible somehow. Maybe we need to implement Measure. LinearLayout *root = Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); root->SetSpacing(3.0f); @@ -330,26 +330,39 @@ void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) { std::string pspButtonName = KeyMap::GetPspButtonName(this->pspBtn_); - parent->Add(new TextView(std::string(km->T("Map a new key for")) + " " + mc->T(pspButtonName), new LinearLayoutParams(Margins(10,0)))); + parent->Add(new TextView(std::string(km->T("Map a new key for")) + " " + mc->T(pspButtonName), new LinearLayoutParams(Margins(10, 0)))); + parent->Add(new TextView(std::string(mapping_.ToVisualString()), new LinearLayoutParams(Margins(10, 0)))); + SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE); } bool KeyMappingNewKeyDialog::key(const KeyInput &key) { - if (mapped_ || time_now_d() < delayUntil_) - return false; + if (time_now_d() < delayUntil_) + return true; if (key.flags & KEY_DOWN) { if (key.keyCode == NKCODE_EXT_MOUSEBUTTON_1) { + // Don't map return true; } - // Only map analog values to this mapping. - if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) - return true; - mapped_ = true; - MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode)); + if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) { + // Only map analog values to this mapping. + return true; + } + + InputMapping newMapping(key.deviceId, key.keyCode); + + if (!(key.flags & KEY_IS_REPEAT)) { + if (!mapping_.mappings.contains(newMapping)) { + mapping_.mappings.push_back(newMapping); + RecreateViews(); + } + } + } + if (key.flags & KEY_UP) { + if (callback_) + callback_(mapping_); TriggerFinish(DR_YES); - if (callback_ && pspBtn_ != VIRTKEY_SPEED_ANALOG) - callback_(kdf); } return true; } @@ -389,10 +402,11 @@ bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) { static bool IgnoreAxisForMapping(int axis) { switch (axis) { - // Ignore the accelerometer for mapping for now. case JOYSTICK_AXIS_ACCELEROMETER_X: case JOYSTICK_AXIS_ACCELEROMETER_Y: case JOYSTICK_AXIS_ACCELEROMETER_Z: + // Ignore the accelerometer for mapping for now. + // We use tilt control for these. return true; default: @@ -400,27 +414,35 @@ static bool IgnoreAxisForMapping(int axis) { } } - void KeyMappingNewKeyDialog::axis(const AxisInput &axis) { - if (mapped_ || time_now_d() < delayUntil_) + if (time_now_d() < delayUntil_) return; if (IgnoreAxisForMapping(axis.axisId)) return; if (axis.value > AXIS_BIND_THRESHOLD) { - mapped_ = true; - MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1)); - TriggerFinish(DR_YES); - if (callback_) - callback_(kdf); - } - - if (axis.value < -AXIS_BIND_THRESHOLD) { - mapped_ = true; - MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1)); - TriggerFinish(DR_YES); - if (callback_) - callback_(kdf); + InputMapping mapping(axis.deviceId, axis.axisId, 1); + triggeredAxes_.insert(mapping); + if (!mapping_.mappings.contains(mapping)) { + mapping_.mappings.push_back(mapping); + RecreateViews(); + } + } else if (axis.value < -AXIS_BIND_THRESHOLD) { + InputMapping mapping(axis.deviceId, axis.axisId, -1); + triggeredAxes_.insert(mapping); + if (!mapping_.mappings.contains(mapping)) { + mapping_.mappings.push_back(mapping); + RecreateViews(); + } + } else if (fabsf(axis.value) < AXIS_BIND_RELEASE_THRESHOLD) { + InputMapping neg(axis.deviceId, axis.axisId, -1); + InputMapping pos(axis.deviceId, axis.axisId, 1); + if (triggeredAxes_.find(neg) != triggeredAxes_.end() || triggeredAxes_.find(pos) != triggeredAxes_.end()) { + // "Key-up" the axis. + TriggerFinish(DR_YES); + if (callback_) + callback_(mapping_); + } } } diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index d02eef8e88..d988e255db 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -76,7 +77,12 @@ protected: private: int pspBtn_; std::function callback_; - bool mapped_ = false; // Prevent double registrations + + KeyMap::MultiInputMapping mapping_; + + // We need to do our own detection for axis "keyup" here. + std::set triggeredAxes_; + double delayUntil_ = 0.0f; }; From 5a82e5856477976939ed04cd59377324aa678424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 17:59:37 +0200 Subject: [PATCH 6/9] Buildfix --- Common/Input/GestureDetector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Common/Input/GestureDetector.cpp b/Common/Input/GestureDetector.cpp index 634f8b25d2..5b99a79993 100644 --- a/Common/Input/GestureDetector.cpp +++ b/Common/Input/GestureDetector.cpp @@ -2,6 +2,8 @@ // TODO: // Zoom gesture a la http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/1847 +#include + #include "Common/TimeUtil.h" #include "Common/Input/GestureDetector.h" From d523005c2bf2af8242ecd06d2a69059ff6b6de52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 19:02:20 +0200 Subject: [PATCH 7/9] Cleanup --- Core/ControlMapper.cpp | 4 ---- Core/KeyMap.h | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index 64c2f3552c..e959fd3b5c 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -298,11 +298,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { // Note: This is an old problem, it didn't appear with the refactoring. if (!changedMapping.IsAxis()) { for (auto &multiMapping : inputMappings) { - bool anyAxis = false; for (auto &mapping : multiMapping.mappings) { - if (mapping.IsAxis()) { - anyAxis = true; - } curInput_[mapping] = ReduceMagnitude(curInput_[mapping]); } } diff --git a/Core/KeyMap.h b/Core/KeyMap.h index b88c0e675e..283ccd35b1 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -149,6 +149,8 @@ namespace KeyMap { FixedTinyVec mappings; }; + // Once the multimappings are inserted here, they must not be empty. + // If one would be, delete the whole entry from the map instead. typedef std::map> KeyMapping; extern KeyMapping g_controllerMap; From c1b5aed9b7d03ba0cefc8bb8ce9be7f9d47583f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 19:56:02 +0200 Subject: [PATCH 8/9] Cleanup, confine g_controllerMap access to KeyMap.cpp --- Core/ControlMapper.cpp | 2 -- Core/KeyMap.cpp | 39 +++++++++++++++++++++++++++++-------- Core/KeyMap.h | 11 ++++++++--- UI/ControlMappingScreen.cpp | 6 ++---- Windows/MainWindowMenu.cpp | 11 +++++------ 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index e959fd3b5c..8d87d53604 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -227,8 +227,6 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { // If a mapping could consist of a combo, we could trivially check it here. for (auto &multiMapping : inputMappings) { - if (multiMapping.empty()) - continue; // Check if the changed mapping was involved in this PSP key. if (multiMapping.mappings.contains(changedMapping)) { changedButtonMask |= mask; diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index 694402eb9a..4cb96497c2 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -515,20 +515,28 @@ bool InputMappingToPspButton(const InputMapping &mapping, std::vector *pspB } bool InputMappingsFromPspButton(int btn, std::vector *mappings, bool ignoreMouse) { + auto iter = g_controllerMap.find(btn); + if (iter == g_controllerMap.end()) { + return false; + } bool mapped = false; - for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) { - if (iter->first == btn) { - for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) { - if (mappings && (!ignoreMouse || iter2->HasMouse())) { - mapped = true; - mappings->push_back(*iter2); - } - } + for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) { + if (mappings && (!ignoreMouse || iter2->HasMouse())) { + mapped = true; + mappings->push_back(*iter2); } } return mapped; } +bool PspButtonHasMappings(int btn) { + auto iter = g_controllerMap.find(btn); + if (iter == g_controllerMap.end()) { + return false; + } + return !iter->second.empty(); +} + MappedAnalogAxes MappedAxesForDevice(int deviceId) { MappedAnalogAxes result{}; @@ -605,6 +613,16 @@ bool ReplaceSingleKeyMapping(int btn, int index, MultiInputMapping key) { return true; } +void DeleteNthMapping(int key, int number) { + auto iter = g_controllerMap.find(key); + if (iter != g_controllerMap.end()) { + if (number < iter->second.size()) { + iter->second.erase(iter->second.begin() + number); + g_controllerMapGeneration++; + } + } +} + void SetInputMapping(int btn, const MultiInputMapping &key, bool replace) { if (key.empty()) { g_controllerMap.erase(btn); @@ -717,6 +735,11 @@ void SaveToIni(IniFile &file) { } } +void ClearAllMappings() { + g_controllerMap.clear(); + g_controllerMapGeneration++; +} + bool IsOuya(const std::string &name) { return name == "OUYA:OUYA Console"; } diff --git a/Core/KeyMap.h b/Core/KeyMap.h index 283ccd35b1..139cbaab3c 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -149,11 +149,11 @@ namespace KeyMap { FixedTinyVec mappings; }; - // Once the multimappings are inserted here, they must not be empty. - // If one would be, delete the whole entry from the map instead. typedef std::map> KeyMapping; - extern KeyMapping g_controllerMap; + // Once the multimappings are inserted here, they must not be empty. + // If one would be, delete the whole entry from the map instead. + // This is automatically handled by SetInputMapping. extern std::set g_seenDeviceIds; extern int g_controllerMapGeneration; // Key & Button names @@ -179,6 +179,9 @@ namespace KeyMap { bool InputMappingToPspButton(const InputMapping &mapping, std::vector *pspButtons); bool InputMappingsFromPspButton(int btn, std::vector *keys, bool ignoreMouse); + // Simplified check. + bool PspButtonHasMappings(int btn); + // Configure the key or axis mapping. // Any configuration will be saved to the Core config. void SetInputMapping(int psp_key, const MultiInputMapping &key, bool replace); @@ -189,6 +192,8 @@ namespace KeyMap { void LoadFromIni(IniFile &iniFile); void SaveToIni(IniFile &iniFile); + void ClearAllMappings(); + void DeleteNthMapping(int key, int number); void SetDefaultKeyMap(DefaultMaps dmap, bool replace); diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index b6d4424172..eebdee261e 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -222,8 +222,7 @@ UI::EventReturn SingleControlMapper::OnAddMouse(UI::EventParams ¶ms) { UI::EventReturn SingleControlMapper::OnDelete(UI::EventParams ¶ms) { int index = atoi(params.v->Tag().c_str()); - KeyMap::g_controllerMap[pspKey_].erase(KeyMap::g_controllerMap[pspKey_].begin() + index); - KeyMap::g_controllerMapGeneration++; + KeyMap::DeleteNthMapping(pspKey_, index); if (index + 1 < (int)rows_.size()) rows_[index]->SetFocus(); @@ -285,8 +284,7 @@ void ControlMappingScreen::update() { } UI::EventReturn ControlMappingScreen::OnClearMapping(UI::EventParams ¶ms) { - KeyMap::g_controllerMap.clear(); - KeyMap::g_controllerMapGeneration++; + KeyMap::ClearAllMappings(); return UI::EVENT_DONE; } diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 1491d2d375..73e5046879 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -170,8 +170,8 @@ namespace MainWindow { } void DoTranslateMenus(HWND hWnd, HMENU menu) { - auto useDefHotkey = [](int virtkey) { - return KeyMap::g_controllerMap[virtkey].empty(); + auto useDefHotkey = [](int virtKey) { + return !KeyMap::PspButtonHasMappings(virtKey); }; TranslateMenuItem(menu, ID_FILE_MENU); @@ -536,8 +536,7 @@ namespace MainWindow { case ID_FILE_SAVESTATE_NEXT_SLOT_HC: { - if (KeyMap::g_controllerMap[VIRTKEY_NEXT_SLOT].empty()) - { + if (!KeyMap::PspButtonHasMappings(VIRTKEY_NEXT_SLOT)) { SaveState::NextSlot(); NativeMessageReceived("savestate_displayslot", ""); } @@ -559,7 +558,7 @@ namespace MainWindow { case ID_FILE_QUICKLOADSTATE_HC: { - if (KeyMap::g_controllerMap[VIRTKEY_LOAD_STATE].empty()) + if (!KeyMap::PspButtonHasMappings(VIRTKEY_LOAD_STATE)) { SetCursor(LoadCursor(0, IDC_WAIT)); SaveState::LoadSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished); @@ -575,7 +574,7 @@ namespace MainWindow { case ID_FILE_QUICKSAVESTATE_HC: { - if (KeyMap::g_controllerMap[VIRTKEY_SAVE_STATE].empty()) + if (!KeyMap::PspButtonHasMappings(VIRTKEY_SAVE_STATE)) { SetCursor(LoadCursor(0, IDC_WAIT)); SaveState::SaveSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished); From 33ba6f6f1a5d2f7163cafacffe718bcf1df9a56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Apr 2023 20:26:18 +0200 Subject: [PATCH 9/9] Fix for menu navigation with DInput controllers on Windows. Bit of a hack. --- Core/KeyMap.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index 4cb96497c2..d359789a53 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -110,6 +110,12 @@ void UpdateNativeMenuKeys() { cancelKeys.push_back(hardcodedCancelKeys[i]); } + // For DInput controllers on Windows. Doesn't clash with XInput because that uses BUTTON_X etc. +#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + confirmKeys.push_back(InputMapping(DEVICE_ID_PAD_0, NKCODE_BUTTON_2)); + cancelKeys.push_back(InputMapping(DEVICE_ID_PAD_0, NKCODE_BUTTON_3)); +#endif + SetDPadKeys(upKeys, downKeys, leftKeys, rightKeys); SetConfirmCancelKeys(confirmKeys, cancelKeys); SetTabLeftRightKeys(tabLeft, tabRight);