Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control map multiple keys to one output #17215

Merged
merged 9 commits into from
Apr 1, 2023
65 changes: 65 additions & 0 deletions Common/Data/Collections/TinySet.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,68 @@ struct TinySet {
T fastLookup_[MaxFastSize];
std::vector<T> *slowLookup_ = nullptr;
};

template <class T, int MaxSize>
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<T, MaxSize> &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];
};
2 changes: 2 additions & 0 deletions Common/Input/GestureDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cstring>

#include "Common/TimeUtil.h"
#include "Common/Input/GestureDetector.h"

Expand Down
18 changes: 18 additions & 0 deletions Common/Input/InputState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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<std::string> 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;
Expand Down
14 changes: 13 additions & 1 deletion Common/Input/InputState.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

#include <unordered_map>
#include <vector>
#include <string>

#include "Common/Math/lin/vec3.h"
#include "Common/Input/KeyCodes.h"
#include "Common/Log.h"

Expand Down Expand Up @@ -105,6 +105,9 @@ class InputMapping {
_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.

Expand All @@ -131,6 +134,15 @@ class InputMapping {
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;
Expand Down
49 changes: 32 additions & 17 deletions Core/ControlMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -219,19 +221,25 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
mappingBit = RotatePSPKeyCode(mappingBit);
}

std::vector<InputMapping> inputMappings;
std::vector<MultiInputMapping> 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) {
// 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;
}
}
Expand All @@ -243,7 +251,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<InputMapping> inputMappings;
std::vector<MultiInputMapping> inputMappings;
if (!KeyMap::InputMappingsFromPspButton(vkId, &inputMappings, false))
continue;

Expand All @@ -253,20 +261,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)) {
unknownbrackets marked this conversation as resolved.
Show resolved Hide resolved
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) {
Expand All @@ -280,8 +295,8 @@ 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) {
for (auto &mapping : multiMapping.mappings) {
curInput_[mapping] = ReduceMagnitude(curInput_[mapping]);
}
}
Expand Down
Loading