[Input] Remove transitional key, mod, joy triplet

Originally, UserInput was built around the key, mod, joy triplet values
to maintain compatibility with other parts of the code base that made
use of these values. Since the UserInput class was introduced, all use
cases have been transitioned off these values in favor of treating the
UserInput as an abstract input.

UserInput is now a simple wrapper around either a KeyboardInput or a
JoyInput structure that only contain data pertinent to their input type.

This concludes the transition to the UserInput type.

Bug: #745
This commit is contained in:
Fabrice de Gans 2024-04-23 13:09:44 -07:00 committed by Fabrice de Gans
parent 72c4f33d63
commit 62294702e4
13 changed files with 403 additions and 358 deletions

View File

@ -2,6 +2,7 @@
#include "wx/opts.h" #include "wx/opts.h"
#include "wx/strutils.h" #include "wx/strutils.h"
#include "wx/wxlogdebug.h"
namespace config { namespace config {

View File

@ -1,4 +1,5 @@
#include "wx/config/shortcuts.h" #include "wx/config/shortcuts.h"
#include "wx/config/user-input.h"
#include <wx/xrc/xmlres.h> #include <wx/xrc/xmlres.h>
@ -11,86 +12,86 @@ namespace internal {
const std::unordered_map<int, UserInput>& DefaultShortcuts() { const std::unordered_map<int, UserInput>& DefaultShortcuts() {
static const std::unordered_map<int, UserInput> kDefaultShortcuts = { static const std::unordered_map<int, UserInput> kDefaultShortcuts = {
{XRCID("CheatsList"), UserInput('C', wxMOD_CMD)}, {XRCID("CheatsList"), KeyboardInput('C', wxMOD_CMD)},
{XRCID("NextFrame"), UserInput('N', wxMOD_CMD)}, {XRCID("NextFrame"), KeyboardInput('N', wxMOD_CMD)},
// this was annoying people A LOT #334 // this was annoying people A LOT #334
//{wxID_EXIT, UserInput(WXK_ESCAPE, wxMOD_NONE)}, //{wxID_EXIT, KeyboardInput(WXK_ESCAPE, wxMOD_NONE)},
// this was annoying people #298 // this was annoying people #298
//{wxID_EXIT, UserInput('X', wxMOD_CMD)}, //{wxID_EXIT, KeyboardInput('X', wxMOD_CMD)},
{wxID_EXIT, UserInput('Q', wxMOD_CMD)}, {wxID_EXIT, KeyboardInput('Q', wxMOD_CMD)},
{wxID_CLOSE, UserInput('W', wxMOD_CMD)}, {wxID_CLOSE, KeyboardInput('W', wxMOD_CMD)},
// load most recent is more commonly used than load state // load most recent is more commonly used than load state
// {XRCID("Load"), UserInput('L', wxMOD_CMD)}, // {XRCID("Load"), KeyboardInput('L', wxMOD_CMD)},
{XRCID("LoadGameRecent"), UserInput('L', wxMOD_CMD)}, {XRCID("LoadGameRecent"), KeyboardInput('L', wxMOD_CMD)},
{XRCID("LoadGame01"), UserInput(WXK_F1, wxMOD_NONE)}, {XRCID("LoadGame01"), KeyboardInput(WXK_F1, wxMOD_NONE)},
{XRCID("LoadGame02"), UserInput(WXK_F2, wxMOD_NONE)}, {XRCID("LoadGame02"), KeyboardInput(WXK_F2, wxMOD_NONE)},
{XRCID("LoadGame03"), UserInput(WXK_F3, wxMOD_NONE)}, {XRCID("LoadGame03"), KeyboardInput(WXK_F3, wxMOD_NONE)},
{XRCID("LoadGame04"), UserInput(WXK_F4, wxMOD_NONE)}, {XRCID("LoadGame04"), KeyboardInput(WXK_F4, wxMOD_NONE)},
{XRCID("LoadGame05"), UserInput(WXK_F5, wxMOD_NONE)}, {XRCID("LoadGame05"), KeyboardInput(WXK_F5, wxMOD_NONE)},
{XRCID("LoadGame06"), UserInput(WXK_F6, wxMOD_NONE)}, {XRCID("LoadGame06"), KeyboardInput(WXK_F6, wxMOD_NONE)},
{XRCID("LoadGame07"), UserInput(WXK_F7, wxMOD_NONE)}, {XRCID("LoadGame07"), KeyboardInput(WXK_F7, wxMOD_NONE)},
{XRCID("LoadGame08"), UserInput(WXK_F8, wxMOD_NONE)}, {XRCID("LoadGame08"), KeyboardInput(WXK_F8, wxMOD_NONE)},
{XRCID("LoadGame09"), UserInput(WXK_F9, wxMOD_NONE)}, {XRCID("LoadGame09"), KeyboardInput(WXK_F9, wxMOD_NONE)},
{XRCID("LoadGame10"), UserInput(WXK_F10, wxMOD_NONE)}, {XRCID("LoadGame10"), KeyboardInput(WXK_F10, wxMOD_NONE)},
{XRCID("Pause"), UserInput(WXK_PAUSE, wxMOD_NONE)}, {XRCID("Pause"), KeyboardInput(WXK_PAUSE, wxMOD_NONE)},
{XRCID("Pause"), UserInput('P', wxMOD_CMD)}, {XRCID("Pause"), KeyboardInput('P', wxMOD_CMD)},
{XRCID("Reset"), UserInput('R', wxMOD_CMD)}, {XRCID("Reset"), KeyboardInput('R', wxMOD_CMD)},
// add shortcuts for original size multiplier #415 // add shortcuts for original size multiplier #415
{XRCID("SetSize1x"), UserInput('1', wxMOD_NONE)}, {XRCID("SetSize1x"), KeyboardInput('1', wxMOD_NONE)},
{XRCID("SetSize2x"), UserInput('2', wxMOD_NONE)}, {XRCID("SetSize2x"), KeyboardInput('2', wxMOD_NONE)},
{XRCID("SetSize3x"), UserInput('3', wxMOD_NONE)}, {XRCID("SetSize3x"), KeyboardInput('3', wxMOD_NONE)},
{XRCID("SetSize4x"), UserInput('4', wxMOD_NONE)}, {XRCID("SetSize4x"), KeyboardInput('4', wxMOD_NONE)},
{XRCID("SetSize5x"), UserInput('5', wxMOD_NONE)}, {XRCID("SetSize5x"), KeyboardInput('5', wxMOD_NONE)},
{XRCID("SetSize6x"), UserInput('6', wxMOD_NONE)}, {XRCID("SetSize6x"), KeyboardInput('6', wxMOD_NONE)},
// save oldest is more commonly used than save other // save oldest is more commonly used than save other
// {XRCID("Save"), UserInput('S', wxMOD_CMD)}, // {XRCID("Save"), KeyboardInput('S', wxMOD_CMD)},
{XRCID("SaveGameOldest"), UserInput('S', wxMOD_CMD)}, {XRCID("SaveGameOldest"), KeyboardInput('S', wxMOD_CMD)},
{XRCID("SaveGame01"), UserInput(WXK_F1, wxMOD_SHIFT)}, {XRCID("SaveGame01"), KeyboardInput(WXK_F1, wxMOD_SHIFT)},
{XRCID("SaveGame02"), UserInput(WXK_F2, wxMOD_SHIFT)}, {XRCID("SaveGame02"), KeyboardInput(WXK_F2, wxMOD_SHIFT)},
{XRCID("SaveGame03"), UserInput(WXK_F3, wxMOD_SHIFT)}, {XRCID("SaveGame03"), KeyboardInput(WXK_F3, wxMOD_SHIFT)},
{XRCID("SaveGame04"), UserInput(WXK_F4, wxMOD_SHIFT)}, {XRCID("SaveGame04"), KeyboardInput(WXK_F4, wxMOD_SHIFT)},
{XRCID("SaveGame05"), UserInput(WXK_F5, wxMOD_SHIFT)}, {XRCID("SaveGame05"), KeyboardInput(WXK_F5, wxMOD_SHIFT)},
{XRCID("SaveGame06"), UserInput(WXK_F6, wxMOD_SHIFT)}, {XRCID("SaveGame06"), KeyboardInput(WXK_F6, wxMOD_SHIFT)},
{XRCID("SaveGame07"), UserInput(WXK_F7, wxMOD_SHIFT)}, {XRCID("SaveGame07"), KeyboardInput(WXK_F7, wxMOD_SHIFT)},
{XRCID("SaveGame08"), UserInput(WXK_F8, wxMOD_SHIFT)}, {XRCID("SaveGame08"), KeyboardInput(WXK_F8, wxMOD_SHIFT)},
{XRCID("SaveGame09"), UserInput(WXK_F9, wxMOD_SHIFT)}, {XRCID("SaveGame09"), KeyboardInput(WXK_F9, wxMOD_SHIFT)},
{XRCID("SaveGame10"), UserInput(WXK_F10, wxMOD_SHIFT)}, {XRCID("SaveGame10"), KeyboardInput(WXK_F10, wxMOD_SHIFT)},
// I prefer the SDL ESC key binding // I prefer the SDL ESC key binding
// {XRCID("ToggleFullscreen"), UserInput(WXK_ESCAPE, wxMOD_NONE)}, // {XRCID("ToggleFullscreen"), KeyboardInput(WXK_ESCAPE, wxMOD_NONE)},
// alt-enter is more standard anyway // alt-enter is more standard anyway
{XRCID("ToggleFullscreen"), UserInput(WXK_RETURN, wxMOD_ALT)}, {XRCID("ToggleFullscreen"), KeyboardInput(WXK_RETURN, wxMOD_ALT)},
{XRCID("JoypadAutofireA"), UserInput('1', wxMOD_ALT)}, {XRCID("JoypadAutofireA"), KeyboardInput('1', wxMOD_ALT)},
{XRCID("JoypadAutofireB"), UserInput('2', wxMOD_ALT)}, {XRCID("JoypadAutofireB"), KeyboardInput('2', wxMOD_ALT)},
{XRCID("JoypadAutofireL"), UserInput('3', wxMOD_ALT)}, {XRCID("JoypadAutofireL"), KeyboardInput('3', wxMOD_ALT)},
{XRCID("JoypadAutofireR"), UserInput('4', wxMOD_ALT)}, {XRCID("JoypadAutofireR"), KeyboardInput('4', wxMOD_ALT)},
{XRCID("VideoLayersBG0"), UserInput('1', wxMOD_CMD)}, {XRCID("VideoLayersBG0"), KeyboardInput('1', wxMOD_CMD)},
{XRCID("VideoLayersBG1"), UserInput('2', wxMOD_CMD)}, {XRCID("VideoLayersBG1"), KeyboardInput('2', wxMOD_CMD)},
{XRCID("VideoLayersBG2"), UserInput('3', wxMOD_CMD)}, {XRCID("VideoLayersBG2"), KeyboardInput('3', wxMOD_CMD)},
{XRCID("VideoLayersBG3"), UserInput('4', wxMOD_CMD)}, {XRCID("VideoLayersBG3"), KeyboardInput('4', wxMOD_CMD)},
{XRCID("VideoLayersOBJ"), UserInput('5', wxMOD_CMD)}, {XRCID("VideoLayersOBJ"), KeyboardInput('5', wxMOD_CMD)},
{XRCID("VideoLayersWIN0"), UserInput('6', wxMOD_CMD)}, {XRCID("VideoLayersWIN0"), KeyboardInput('6', wxMOD_CMD)},
{XRCID("VideoLayersWIN1"), UserInput('7', wxMOD_CMD)}, {XRCID("VideoLayersWIN1"), KeyboardInput('7', wxMOD_CMD)},
{XRCID("VideoLayersOBJWIN"), UserInput('8', wxMOD_CMD)}, {XRCID("VideoLayersOBJWIN"), KeyboardInput('8', wxMOD_CMD)},
{XRCID("Rewind"), UserInput('B', wxMOD_CMD)}, {XRCID("Rewind"), KeyboardInput('B', wxMOD_CMD)},
// following are not in standard menus // following are not in standard menus
// FILExx are filled in when recent menu is filled // FILExx are filled in when recent menu is filled
{wxID_FILE1, UserInput(WXK_F1, wxMOD_CMD)}, {wxID_FILE1, KeyboardInput(WXK_F1, wxMOD_CMD)},
{wxID_FILE2, UserInput(WXK_F2, wxMOD_CMD)}, {wxID_FILE2, KeyboardInput(WXK_F2, wxMOD_CMD)},
{wxID_FILE3, UserInput(WXK_F3, wxMOD_CMD)}, {wxID_FILE3, KeyboardInput(WXK_F3, wxMOD_CMD)},
{wxID_FILE4, UserInput(WXK_F4, wxMOD_CMD)}, {wxID_FILE4, KeyboardInput(WXK_F4, wxMOD_CMD)},
{wxID_FILE5, UserInput(WXK_F5, wxMOD_CMD)}, {wxID_FILE5, KeyboardInput(WXK_F5, wxMOD_CMD)},
{wxID_FILE6, UserInput(WXK_F6, wxMOD_CMD)}, {wxID_FILE6, KeyboardInput(WXK_F6, wxMOD_CMD)},
{wxID_FILE7, UserInput(WXK_F7, wxMOD_CMD)}, {wxID_FILE7, KeyboardInput(WXK_F7, wxMOD_CMD)},
{wxID_FILE8, UserInput(WXK_F8, wxMOD_CMD)}, {wxID_FILE8, KeyboardInput(WXK_F8, wxMOD_CMD)},
{wxID_FILE9, UserInput(WXK_F9, wxMOD_CMD)}, {wxID_FILE9, KeyboardInput(WXK_F9, wxMOD_CMD)},
{wxID_FILE10, UserInput(WXK_F10, wxMOD_CMD)}, {wxID_FILE10, KeyboardInput(WXK_F10, wxMOD_CMD)},
{XRCID("VideoLayersReset"), UserInput('0', wxMOD_CMD)}, {XRCID("VideoLayersReset"), KeyboardInput('0', wxMOD_CMD)},
{XRCID("ChangeFilter"), UserInput('G', wxMOD_CMD)}, {XRCID("ChangeFilter"), KeyboardInput('G', wxMOD_CMD)},
{XRCID("ChangeIFB"), UserInput('I', wxMOD_CMD)}, {XRCID("ChangeIFB"), KeyboardInput('I', wxMOD_CMD)},
{XRCID("IncreaseVolume"), UserInput(WXK_NUMPAD_ADD, wxMOD_NONE)}, {XRCID("IncreaseVolume"), KeyboardInput(WXK_NUMPAD_ADD, wxMOD_NONE)},
{XRCID("DecreaseVolume"), UserInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE)}, {XRCID("DecreaseVolume"), KeyboardInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE)},
{XRCID("ToggleSound"), UserInput(WXK_NUMPAD_ENTER, wxMOD_NONE)}, {XRCID("ToggleSound"), KeyboardInput(WXK_NUMPAD_ENTER, wxMOD_NONE)},
}; };
return kDefaultShortcuts; return kDefaultShortcuts;
} }

View File

@ -40,7 +40,7 @@ std::vector<std::pair<int, wxString>> Shortcuts::GetConfiguration() const {
config.reserve(command_to_inputs_.size() + 1); config.reserve(command_to_inputs_.size() + 1);
if (!disabled_defaults_.empty()) { if (!disabled_defaults_.empty()) {
std::set<UserInput> noop_inputs; std::unordered_set<UserInput> noop_inputs;
for (const auto& iter : disabled_defaults_) { for (const auto& iter : disabled_defaults_) {
noop_inputs.insert(iter.first); noop_inputs.insert(iter.first);
} }
@ -48,7 +48,7 @@ std::vector<std::pair<int, wxString>> Shortcuts::GetConfiguration() const {
} }
for (const auto& iter : command_to_inputs_) { for (const auto& iter : command_to_inputs_) {
std::set<UserInput> inputs; std::unordered_set<UserInput> inputs;
for (const auto& input : iter.second) { for (const auto& input : iter.second) {
if (internal::DefaultShortcutForCommand(iter.first) != input) { if (internal::DefaultShortcutForCommand(iter.first) != input) {
// Not a default input. // Not a default input.

View File

@ -109,52 +109,6 @@ wxString ModToLocalizedString(int mod) {
return config_string; return config_string;
} }
wxString KeyboardInputToConfigString(int mod, int key) {
// Handle the modifier case separately.
if (KeyIsModifier(key)) {
return wxString::Format("%d:%d", key, mod);
}
// Custom overrides.
const auto iter = kKeyCodeOverrides.find(static_cast<wxKeyCode>(key));
if (iter != kKeyCodeOverrides.end()) {
return ModToConfigString(mod) + iter->second.config_name;
}
const wxString accel_string = wxAcceleratorEntry(mod, key).ToRawString().MakeUpper();
if (!accel_string.IsAscii()) {
// Unicode handling.
return wxString::Format("%d:%d", key, mod);
}
return accel_string;
}
wxString KeyboardInputToLocalizedString(int mod, int key) {
static const std::map<int, wxString> kStandaloneModifiers = {
{WXK_ALT, _("Alt")},
{WXK_SHIFT, _("Shift")},
#ifdef __WXMAC__
{WXK_RAW_CONTROL, _("Ctrl")},
{WXK_COMMAND, _("Cmd")},
#else
{WXK_CONTROL, _("Ctrl")},
#endif
};
const auto iter = kStandaloneModifiers.find(key);
if (iter != kStandaloneModifiers.end()) {
return iter->second;
}
// Custom overrides.
const auto iter2 = kKeyCodeOverrides.find(static_cast<wxKeyCode>(key));
if (iter2 != kKeyCodeOverrides.end()) {
return ModToLocalizedString(mod) + iter2->second.display_name;
}
return wxAcceleratorEntry(mod, key).ToString();
}
int StringToInt(const wxString& string) { int StringToInt(const wxString& string) {
int ret = 0; int ret = 0;
for (const auto& c : string) { for (const auto& c : string) {
@ -178,14 +132,14 @@ UserInput StringToUserInput(const wxString& string) {
// Standalone modifiers do not get parsed properly by wxWidgets. // Standalone modifiers do not get parsed properly by wxWidgets.
static const std::map<wxString, UserInput> kStandaloneModifiers = { static const std::map<wxString, UserInput> kStandaloneModifiers = {
{"ALT", UserInput(WXK_ALT, wxMOD_ALT)}, {"ALT", KeyboardInput(WXK_ALT, wxMOD_ALT)},
{"SHIFT", UserInput(WXK_SHIFT, wxMOD_SHIFT)}, {"SHIFT", KeyboardInput(WXK_SHIFT, wxMOD_SHIFT)},
{"RAWCTRL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)}, {"RAWCTRL", KeyboardInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAW_CTRL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)}, {"RAW_CTRL", KeyboardInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAWCONTROL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)}, {"RAWCONTROL", KeyboardInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAW_CONTROL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)}, {"RAW_CONTROL", KeyboardInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"CTRL", UserInput(WXK_CONTROL, wxMOD_CONTROL)}, {"CTRL", KeyboardInput(WXK_CONTROL, wxMOD_CONTROL)},
{"CONTROL", UserInput(WXK_CONTROL, wxMOD_CONTROL)}, {"CONTROL", KeyboardInput(WXK_CONTROL, wxMOD_CONTROL)},
}; };
if (string.empty()) { if (string.empty()) {
@ -197,30 +151,31 @@ UserInput StringToUserInput(const wxString& string) {
size_t start, length; size_t start, length;
kJoyRegex.GetMatch(&start, &length, 1); kJoyRegex.GetMatch(&start, &length, 1);
const int joy = StringToInt(string.Mid(start, length)); const int joy = StringToInt(string.Mid(start, length));
const JoyId joy_id(joy - 1);
const wxString remainder = string.Mid(start + length + 1); const wxString remainder = string.Mid(start + length + 1);
if (kAxisRegex.Matches(remainder)) { if (kAxisRegex.Matches(remainder)) {
kAxisRegex.GetMatch(&start, &length, 1); kAxisRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); const int key = StringToInt(remainder.Mid(start, length));
const JoyControl control = const JoyControl control =
remainder[start + length] == '+' ? JoyControl::AxisPlus : JoyControl::AxisMinus; remainder[start + length] == '+' ? JoyControl::AxisPlus : JoyControl::AxisMinus;
return UserInput(key, control, JoyId(joy - 1)); return JoyInput(joy_id, control, key);
} }
if (kButtonRegex.Matches(remainder)) { if (kButtonRegex.Matches(remainder)) {
kButtonRegex.GetMatch(&start, &length, 1); kButtonRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); const int key = StringToInt(remainder.Mid(start, length));
return UserInput(key, JoyControl::Button, JoyId(joy - 1)); return JoyInput(joy_id, JoyControl::Button, key);
} }
if (kHatRegex.Matches(remainder)) { if (kHatRegex.Matches(remainder)) {
kHatRegex.GetMatch(&start, &length, 1); kHatRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); const int key = StringToInt(remainder.Mid(start, length));
if (kHatRegex.GetMatch(remainder, 3).Length()) { if (kHatRegex.GetMatch(remainder, 3).Length()) {
return UserInput(key, JoyControl::HatNorth, JoyId(joy - 1)); return JoyInput(joy_id, JoyControl::HatNorth, key);
} else if (kHatRegex.GetMatch(remainder, 4).Length()) { } else if (kHatRegex.GetMatch(remainder, 4).Length()) {
return UserInput(key, JoyControl::HatSouth, JoyId(joy - 1)); return JoyInput(joy_id, JoyControl::HatSouth, key);
} else if (kHatRegex.GetMatch(remainder, 5).Length()) { } else if (kHatRegex.GetMatch(remainder, 5).Length()) {
return UserInput(key, JoyControl::HatEast, JoyId(joy - 1)); return JoyInput(joy_id, JoyControl::HatEast, key);
} else if (kHatRegex.GetMatch(remainder, 6).Length()) { } else if (kHatRegex.GetMatch(remainder, 6).Length()) {
return UserInput(key, JoyControl::HatWest, JoyId(joy - 1)); return JoyInput(joy_id, JoyControl::HatWest, key);
} }
} }
@ -230,11 +185,13 @@ UserInput StringToUserInput(const wxString& string) {
// Not a joystick. // Not a joystick.
// Non-ASCII keyboard input are treated as a pair of integers "key:mod".
const auto pair = strutils::split(string, ":"); const auto pair = strutils::split(string, ":");
long mod, key; long mod = 0;
long key = 0;
if (pair.size() == 2 && pair[0].ToLong(&key) && pair[1].ToLong(&mod)) { if (pair.size() == 2 && pair[0].ToLong(&key) && pair[1].ToLong(&mod)) {
// Pair of integers, likely a unicode input. // Pair of integers, likely a unicode input.
return UserInput(key, mod); return KeyboardInput(static_cast<wxKeyCode>(key), static_cast<wxKeyModifier>(mod));
} }
wxString upper(string); wxString upper(string);
@ -257,7 +214,8 @@ UserInput StringToUserInput(const wxString& string) {
if (key < WXK_START && wxIslower(key)) { if (key < WXK_START && wxIslower(key)) {
key = wxToupper(key); key = wxToupper(key);
} }
return UserInput(key, accel.GetFlags()); return KeyboardInput(static_cast<wxKeyCode>(key),
static_cast<wxKeyModifier>(accel.GetFlags()));
} }
// Invalid. // Invalid.
@ -272,45 +230,77 @@ JoyId JoyId::Invalid() {
return JoyId(kInvalidSdlIndex); return JoyId(kInvalidSdlIndex);
} }
wxString JoyId::ToString() { wxString JoyId::ToString() const {
return wxString::Format("Joy%d", sdl_index_ + 1); return wxString::Format("Joy%d", sdl_index_ + 1);
} }
bool JoyId::operator==(const JoyId& other) const { wxString JoyInput::ToString() const {
return sdl_index_ == other.sdl_index_; const wxString joy_string = joy_.ToString();
} switch (control_) {
bool JoyId::operator!=(const JoyId& other) const { case JoyControl::AxisPlus:
return !(*this == other); return wxString::Format("%s-Axis%d+", joy_string, control_index_);
} case JoyControl::AxisMinus:
bool JoyId::operator<(const JoyId& other) const { return wxString::Format("%s-Axis%d-", joy_string, control_index_);
return sdl_index_ < other.sdl_index_; case JoyControl::Button:
} return wxString::Format("%s-Button%d", joy_string, control_index_);
bool JoyId::operator<=(const JoyId& other) const { case JoyControl::HatNorth:
return !(*this > other); return wxString::Format("%s-Hat%dN", joy_string, control_index_);
} case JoyControl::HatSouth:
bool JoyId::operator>(const JoyId& other) const { return wxString::Format("%s-Hat%dS", joy_string, control_index_);
return other < *this; case JoyControl::HatWest:
} return wxString::Format("%s-Hat%dW", joy_string, control_index_);
bool JoyId::operator>=(const JoyId& other) const { case JoyControl::HatEast:
return !(*this < other); return wxString::Format("%s-Hat%dE", joy_string, control_index_);
}
// Unreachable.
assert(false);
return wxEmptyString;
} }
JoyId::JoyId(int sdl_index) : sdl_index_(sdl_index) {} wxString KeyboardInput::ToConfigString() const {
// Handle the modifier case separately.
if (KeyIsModifier(key_)) {
return wxString::Format("%d:%d", key_, mod_);
}
UserInput::UserInput(uint8_t control_index, JoyControl control, JoyId joystick) // Custom overrides.
: UserInput(Device::Joystick, const auto iter = kKeyCodeOverrides.find(key_);
static_cast<int>(control), if (iter != kKeyCodeOverrides.end()) {
control_index, return ModToConfigString(mod_) + iter->second.config_name;
joystick.sdl_index_ + 1) {} }
UserInput::UserInput(wxKeyCode key, wxKeyModifier mod) const wxString accel_string = wxAcceleratorEntry(mod_, key_).ToRawString().MakeUpper();
: UserInput(Device::Keyboard, mod, key, 0) {} if (!accel_string.IsAscii()) {
// Unicode handling.
return wxString::Format("%d:%d", key_, mod_);
}
return accel_string;
}
UserInput::UserInput(char c, wxKeyModifier mod) : UserInput(Device::Keyboard, mod, c, 0) {} wxString KeyboardInput::ToLocalizedString() const {
// Handle the modifier case separately.
if (KeyIsModifier(key_)) {
return ModToLocalizedString(mod_) + _("Key");
}
// Custom overrides.
const auto iter = kKeyCodeOverrides.find(key_);
if (iter != kKeyCodeOverrides.end()) {
return ModToLocalizedString(mod_) + iter->second.display_name;
}
const wxString accel_string = wxAcceleratorEntry(mod_, key_).ToRawString().MakeUpper();
if (!accel_string.IsAscii()) {
// Unicode handling.
return wxString::Format("%d:%d", key_, mod_);
}
return accel_string;
}
// static // static
std::set<UserInput> UserInput::FromConfigString(const wxString& string) { std::unordered_set<UserInput> UserInput::FromConfigString(const wxString& string) {
std::set<UserInput> user_inputs; std::unordered_set<UserInput> user_inputs;
if (string.empty()) { if (string.empty()) {
return user_inputs; return user_inputs;
@ -328,7 +318,7 @@ std::set<UserInput> UserInput::FromConfigString(const wxString& string) {
} }
// static // static
wxString UserInput::SpanToConfigString(const std::set<UserInput>& user_inputs) { wxString UserInput::SpanToConfigString(const std::unordered_set<UserInput>& user_inputs) {
wxString config_string; wxString config_string;
if (user_inputs.empty()) { if (user_inputs.empty()) {
return config_string; return config_string;
@ -344,34 +334,9 @@ wxString UserInput::ToConfigString() const {
case Device::Invalid: case Device::Invalid:
return wxEmptyString; return wxEmptyString;
case Device::Keyboard: case Device::Keyboard:
return KeyboardInputToConfigString(mod_, key_); return keyboard_input().ToConfigString();
case Device::Joystick: case Device::Joystick:
wxString key; return joy_input().ToString();
switch (static_cast<JoyControl>(mod_)) {
case JoyControl::AxisPlus:
key = wxString::Format(("Axis%d+"), key_);
break;
case JoyControl::AxisMinus:
key = wxString::Format(("Axis%d-"), key_);
break;
case JoyControl::Button:
key = wxString::Format(("Button%d"), key_);
break;
case JoyControl::HatNorth:
key = wxString::Format(("Hat%dN"), key_);
break;
case JoyControl::HatSouth:
key = wxString::Format(("Hat%dS"), key_);
break;
case JoyControl::HatWest:
key = wxString::Format(("Hat%dW"), key_);
break;
case JoyControl::HatEast:
key = wxString::Format(("Hat%dE"), key_);
break;
}
return wxString::Format("Joy%d-%s", joy_, key);
} }
// Unreachable. // Unreachable.
@ -384,10 +349,9 @@ wxString UserInput::ToLocalizedString() const {
case Device::Invalid: case Device::Invalid:
return wxEmptyString; return wxEmptyString;
case Device::Keyboard: case Device::Keyboard:
return KeyboardInputToLocalizedString(mod_, key_); return keyboard_input().ToLocalizedString();
case Device::Joystick: case Device::Joystick:
// Same as Config string. return joy_input().ToString();
return ToConfigString();
} }
// Unreachable. // Unreachable.

View File

@ -1,17 +1,53 @@
#ifndef VBAM_WX_CONFIG_USER_INPUT_H_ #ifndef VBAM_WX_CONFIG_USER_INPUT_H_
#define VBAM_WX_CONFIG_USER_INPUT_H_ #define VBAM_WX_CONFIG_USER_INPUT_H_
#include <cassert>
#include <cstdint> #include <cstdint>
#include <set> #include <unordered_set>
#include <variant.hpp>
#include <wx/event.h>
#include <wx/log.h>
#include <wx/string.h> #include <wx/string.h>
namespace config { namespace config {
// Forward declaration. // Abstract representation of a keyboard input. This class is used to represent
class UserInput; // a key press or release event. It is used in the configuration system to
// represent a key binding.
class KeyboardInput final {
public:
constexpr explicit KeyboardInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE)
: key_(key), mod_(mod) {}
constexpr explicit KeyboardInput(char c, wxKeyModifier mod = wxMOD_NONE)
: key_(static_cast<wxKeyCode>(c)), mod_(mod) {}
~KeyboardInput() = default;
constexpr wxKeyCode key() const { return key_; }
constexpr wxKeyModifier mod() const { return mod_; }
wxString ToConfigString() const;
wxString ToLocalizedString() const;
bool operator==(const KeyboardInput& other) const {
return key_ == other.key_ && mod_ == other.mod_;
}
bool operator!=(const KeyboardInput& other) const { return !(*this == other); }
bool operator<(const KeyboardInput& other) const {
if (key_ == other.key_) {
return mod_ < other.mod_;
} else {
return key_ < other.key_;
}
}
bool operator<=(const KeyboardInput& other) const { return *this < other || *this == other; }
bool operator>(const KeyboardInput& other) const { return !(*this <= other); }
bool operator>=(const KeyboardInput& other) const { return !(*this < other); }
private:
const wxKeyCode key_;
const wxKeyModifier mod_;
};
// One of the possible joystick controls. // One of the possible joystick controls.
enum class JoyControl { enum class JoyControl {
@ -27,146 +63,192 @@ enum class JoyControl {
// Abstraction for a single joystick. In the current implementation, this // Abstraction for a single joystick. In the current implementation, this
// encapsulates an `sdl_index_`. // encapsulates an `sdl_index_`.
class JoyId { class JoyId final {
public: public:
static JoyId Invalid(); static JoyId Invalid();
explicit JoyId(int sdl_index); constexpr explicit JoyId(int sdl_index) : sdl_index_(sdl_index){};
virtual ~JoyId() = default; ~JoyId() = default;
wxString ToString(); wxString ToString() const;
bool operator==(const JoyId& other) const; constexpr bool operator==(const JoyId& other) const { return sdl_index_ == other.sdl_index_; }
bool operator!=(const JoyId& other) const; constexpr bool operator!=(const JoyId& other) const { return sdl_index_ != other.sdl_index_; }
bool operator<(const JoyId& other) const; constexpr bool operator<(const JoyId& other) const { return sdl_index_ < other.sdl_index_; }
bool operator<=(const JoyId& other) const; constexpr bool operator<=(const JoyId& other) const { return sdl_index_ <= other.sdl_index_; }
bool operator>(const JoyId& other) const; constexpr bool operator>(const JoyId& other) const { return sdl_index_ > other.sdl_index_; }
bool operator>=(const JoyId& other) const; constexpr bool operator>=(const JoyId& other) const { return sdl_index_ >= other.sdl_index_; }
private: private:
JoyId() = delete; JoyId() = delete;
int sdl_index_; const int sdl_index_;
friend class UserInput; friend struct std::hash<config::JoyId>;
};
// Abstraction for a joystick input. This class is used to represent a joystick
// control press or release event. It is used in the configuration system to
// represent a joystick binding.
class JoyInput final {
public:
constexpr JoyInput(JoyId joy, JoyControl control, uint8_t control_index)
: joy_(joy), control_(control), control_index_(control_index) {}
~JoyInput() = default;
constexpr JoyId joy() const { return joy_; }
constexpr JoyControl control() const { return control_; }
constexpr uint8_t control_index() const { return control_index_; }
wxString ToString() const;
constexpr bool operator==(const JoyInput& other) const {
return joy_ == other.joy_ && control_ == other.control_ &&
control_index_ == other.control_index_;
}
constexpr bool operator!=(const JoyInput& other) const { return !(*this == other); }
constexpr bool operator<(const JoyInput& other) const {
if (joy_ == other.joy_) {
if (control_ == other.control_) {
return control_index_ < other.control_index_;
} else {
return control_ < other.control_;
}
} else {
return joy_ < other.joy_;
}
}
constexpr bool operator<=(const JoyInput& other) const {
return *this < other || *this == other;
}
constexpr bool operator>(const JoyInput& other) const { return !(*this <= other); }
constexpr bool operator>=(const JoyInput& other) const { return !(*this < other); }
private:
const JoyId joy_;
const JoyControl control_;
const uint8_t control_index_;
}; };
// Abstraction for a user input, which can come from a keyboard or a joystick. // Abstraction for a user input, which can come from a keyboard or a joystick.
// This class implements comparison operators so it can be used in sets and as // TODO: Most of these methods should be constexpr but nonstd::variant is not.
// a key in maps.
//
// TODO: Right now, this class is implemented as a thin wrapper around the key,
// mod and joy user input representation used in many places in the code base.
// This is to ease a transition away from the key, mod, joy triplet, which
// UserInput will eventually replace.
class UserInput { class UserInput {
public: public:
// The device type for a user control. // The device type for a user control.
enum class Device { Invalid = 0, Keyboard, Joystick, Last = Joystick }; enum class Device { Invalid = 0, Keyboard, Joystick, Last = Joystick };
// Constructor from a configuration string. Returns empty set on failure. // Constructor from a configuration string. Returns empty set on failure.
static std::set<UserInput> FromConfigString(const wxString& string); static std::unordered_set<UserInput> FromConfigString(const wxString& string);
// Converts a set of UserInput into a configuration string. This // Converts a set of UserInput into a configuration string. This
// recomputes the configuration string every time and should not be used // recomputes the configuration string every time and should not be used
// for comparison purposes. // for comparison purposes.
// TODO: Replace std::set with std::span when the code base uses C++20. // TODO: Replace std::unordered_set with std::span when the code base uses C++20.
static wxString SpanToConfigString(const std::set<UserInput>& user_inputs); static wxString SpanToConfigString(const std::unordered_set<UserInput>& user_inputs);
// Invalid UserInput, mainly used for comparison. // Invalid UserInput, mainly used for comparison.
UserInput() : UserInput(Device::Invalid, 0, 0, 0) {} UserInput() : device_(Device::Invalid), input_(nonstd::monostate{}) {}
// Constructor for a joystick input. // Constructors for joystick and keyboard inputs. This object can be
UserInput(uint8_t control_index, JoyControl control, JoyId joystick); // implicitly constructed from JoyInput and KeyboardInput.
UserInput(JoyInput joy_input) : device_(Device::Joystick), input_(joy_input) {}
// Constructors for a keyboard input. UserInput(KeyboardInput keyboard_input)
UserInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE); : device_(Device::Keyboard), input_(keyboard_input) {}
UserInput(char c, wxKeyModifier mod = wxMOD_NONE);
// TODO: Remove this once all uses have been removed.
explicit UserInput(int key, int mod = 0, int joy = 0)
: UserInput(joy == 0 ? Device::Keyboard : Device::Joystick,
mod,
key,
joy) {}
Device device() const { return device_; } Device device() const { return device_; }
const KeyboardInput& keyboard_input() const {
assert(is_keyboard());
return nonstd::get<KeyboardInput>(input_);
};
const JoyInput& joy_input() const {
assert(is_joystick());
return nonstd::get<JoyInput>(input_);
};
bool is_valid() const { return device_ != Device::Invalid; }
operator bool() const { return is_valid(); }
bool is_keyboard() const { return device_ == Device::Keyboard; }
bool is_joystick() const { return device_ == Device::Joystick; }
// Converts to a configuration string for saving. // Converts to a configuration string for saving.
wxString ToConfigString() const; wxString ToConfigString() const;
// Converts to a localized string for display. // Converts to a localized string for display.
wxString ToLocalizedString() const; wxString ToLocalizedString() const;
JoyId joystick() const { return joystick_; } // Comparison operators.
constexpr bool is_valid() const { return device_ != Device::Invalid; } bool operator==(const UserInput& other) const {
constexpr operator bool() const { return is_valid(); } return device_ == other.device_ && input_ == other.input_;
bool is_joystick() const { return device_ == Device::Joystick; }
bool is_keyboard() const { return device_ == Device::Keyboard; }
int key() const { return key_; }
int mod() const { return mod_; }
unsigned joy() const { return joy_; }
JoyControl joy_control() const {
assert(is_joystick());
return static_cast<JoyControl>(mod_);
} }
bool operator!=(const UserInput& other) const { return !(*this == other); }
wxKeyCode key_code() const { bool operator<(const UserInput& other) const {
assert(is_keyboard()); if (device_ == other.device_) {
return static_cast<wxKeyCode>(key_); return input_ < other.input_;
} } else {
constexpr bool operator==(const UserInput& other) const {
return device_ == other.device_ && mod_ == other.mod_ &&
key_ == other.key_ && joy_ == other.joy_;
}
constexpr bool operator!=(const UserInput& other) const {
return !(*this == other);
}
constexpr bool operator<(const UserInput& other) const {
if (device_ < other.device_) {
return device_ < other.device_; return device_ < other.device_;
} }
if (joy_ != other.joy_) {
return joy_ < other.joy_;
}
if (key_ != other.key_) {
return key_ < other.key_;
}
if (mod_ != other.mod_) {
return mod_ < other.mod_;
}
return false;
} }
constexpr bool operator<=(const UserInput& other) const { bool operator<=(const UserInput& other) const {
return !(*this > other); return *this < other || *this == other;
}
constexpr bool operator>(const UserInput& other) const {
return other < *this;
}
constexpr bool operator>=(const UserInput& other) const {
return !(*this < other);
} }
bool operator>(const UserInput& other) const { return !(*this <= other); }
bool operator>=(const UserInput& other) const { return !(*this < other); }
private: private:
UserInput(Device device, int mod, int key, unsigned joy) const Device device_;
: device_(device), const nonstd::variant<nonstd::monostate, JoyInput, KeyboardInput> input_;
joystick_(joy == 0 ? JoyId::Invalid()
: JoyId(joy - 1)),
mod_(mod),
key_(key),
joy_(joy) {}
Device device_;
JoyId joystick_;
int mod_;
int key_;
unsigned joy_;
}; };
} // namespace config } // namespace config
// Specializations for hash functions for all of the above classes.
template <>
struct std::hash<config::JoyId> {
std::size_t operator()(config::JoyId const& joy_id) const noexcept {
return std::hash<int>{}(joy_id.sdl_index_);
}
};
template <>
struct std::hash<config::JoyInput> {
std::size_t operator()(config::JoyInput const& joy_input) const noexcept {
const std::size_t hash1 = std::hash<config::JoyId>{}(joy_input.joy());
const std::size_t hash2 = std::hash<int>{}(static_cast<int>(joy_input.control()));
const std::size_t hash3 = std::hash<int>{}(joy_input.control_index());
return hash1 ^ hash2 ^ hash3;
}
};
template <>
struct std::hash<config::KeyboardInput> {
std::size_t operator()(config::KeyboardInput const& keyboard_input) const noexcept {
const std::size_t hash1 = std::hash<int>{}(keyboard_input.key());
const std::size_t hash2 = std::hash<int>{}(keyboard_input.mod());
return hash1 ^ hash2;
}
};
template <>
struct std::hash<config::UserInput> {
std::size_t operator()(config::UserInput const& user_input) const noexcept {
const std::size_t device_hash = std::hash<int>{}(static_cast<int>(user_input.device()));
switch (user_input.device()) {
case config::UserInput::Device::Invalid:
return device_hash;
case config::UserInput::Device::Joystick:
return device_hash ^ std::hash<config::JoyInput>{}(user_input.joy_input());
case config::UserInput::Device::Keyboard:
return device_hash ^
std::hash<config::KeyboardInput>{}(user_input.keyboard_input());
}
// Unreachable.
assert(false);
return 0;
}
};
#endif // VBAM_WX_CONFIG_USER_INPUT_H_ #endif // VBAM_WX_CONFIG_USER_INPUT_H_

View File

@ -107,68 +107,68 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg,
opts_t gopts; opts_t gopts;
const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBindings = { const std::map<config::GameControl, std::unordered_set<config::UserInput>> kDefaultBindings = {
{config::GameControl(0, config::GameKey::Up), {config::GameControl(0, config::GameKey::Up),
{ {
config::UserInput('W'), config::KeyboardInput('W'),
config::UserInput(11, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 11),
config::UserInput(1, config::JoyControl::AxisMinus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 1),
config::UserInput(3, config::JoyControl::AxisMinus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 3),
}}, }},
{config::GameControl(0, config::GameKey::Down), {config::GameControl(0, config::GameKey::Down),
{ {
config::UserInput('S'), config::KeyboardInput('S'),
config::UserInput(12, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 12),
config::UserInput(1, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 1),
config::UserInput(3, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 3),
}}, }},
{config::GameControl(0, config::GameKey::Left), {config::GameControl(0, config::GameKey::Left),
{ {
config::UserInput('A'), config::KeyboardInput('A'),
config::UserInput(13, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 13),
config::UserInput(0, config::JoyControl::AxisMinus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 0),
config::UserInput(2, config::JoyControl::AxisMinus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 2),
}}, }},
{config::GameControl(0, config::GameKey::Right), {config::GameControl(0, config::GameKey::Right),
{ {
config::UserInput('D'), config::KeyboardInput('D'),
config::UserInput(14, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 14),
config::UserInput(0, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 0),
config::UserInput(2, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 2),
}}, }},
{config::GameControl(0, config::GameKey::A), {config::GameControl(0, config::GameKey::A),
{ {
config::UserInput('L'), config::KeyboardInput('L'),
config::UserInput(0, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 0),
}}, }},
{config::GameControl(0, config::GameKey::B), {config::GameControl(0, config::GameKey::B),
{ {
config::UserInput('K'), config::KeyboardInput('K'),
config::UserInput(1, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 1),
}}, }},
{config::GameControl(0, config::GameKey::L), {config::GameControl(0, config::GameKey::L),
{ {
config::UserInput('I'), config::KeyboardInput('I'),
config::UserInput(2, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 2),
config::UserInput(9, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 9),
config::UserInput(4, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 4),
}}, }},
{config::GameControl(0, config::GameKey::R), {config::GameControl(0, config::GameKey::R),
{ {
config::UserInput('O'), config::KeyboardInput('O'),
config::UserInput(3, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 3),
config::UserInput(10, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 10),
config::UserInput(5, config::JoyControl::AxisPlus, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 5),
}}, }},
{config::GameControl(0, config::GameKey::Select), {config::GameControl(0, config::GameKey::Select),
{ {
config::UserInput(WXK_BACK), config::KeyboardInput(WXK_BACK),
config::UserInput(4, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 4),
}}, }},
{config::GameControl(0, config::GameKey::Start), {config::GameControl(0, config::GameKey::Start),
{ {
config::UserInput(WXK_RETURN), config::KeyboardInput(WXK_RETURN),
config::UserInput(6, config::JoyControl::Button, config::JoyId(0)), config::JoyInput(config::JoyId(0), config::JoyControl::Button, 6),
}}, }},
{config::GameControl(0, config::GameKey::MotionUp), {}}, {config::GameControl(0, config::GameKey::MotionUp), {}},
{config::GameControl(0, config::GameKey::MotionDown), {}}, {config::GameControl(0, config::GameKey::MotionDown), {}},
@ -180,7 +180,7 @@ const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBinding
{config::GameControl(0, config::GameKey::AutoB), {}}, {config::GameControl(0, config::GameKey::AutoB), {}},
{config::GameControl(0, config::GameKey::Speed), {config::GameControl(0, config::GameKey::Speed),
{ {
config::UserInput(WXK_SPACE), config::KeyboardInput(WXK_SPACE),
}}, }},
{config::GameControl(0, config::GameKey::Capture), {}}, {config::GameControl(0, config::GameKey::Capture), {}},
{config::GameControl(0, config::GameKey::Gameshark), {}}, {config::GameControl(0, config::GameKey::Gameshark), {}},
@ -562,14 +562,11 @@ void update_opts() {
void update_joypad_opts() { void update_joypad_opts() {
wxConfigBase* cfg = wxConfigBase::Get(); wxConfigBase* cfg = wxConfigBase::Get();
// For joypad, compare the UserInput sets. Since UserInput guarantees a // For joypad, compare the UserInput sets.
// certain ordering, it is possible that the user control in the panel shows
// a different ordering than the one that will be eventually saved, but this
// is nothing to worry about.
bool game_bindings_changed = false; bool game_bindings_changed = false;
for (const auto &iter : gopts.game_control_bindings) { for (const auto &iter : gopts.game_control_bindings) {
wxString option_name = iter.first.ToString(); wxString option_name = iter.first.ToString();
std::set<config::UserInput> saved_config = std::unordered_set<config::UserInput> saved_config =
config::UserInput::FromConfigString(cfg->Read(option_name, "")); config::UserInput::FromConfigString(cfg->Read(option_name, ""));
if (saved_config != iter.second) { if (saved_config != iter.second) {
game_bindings_changed = true; game_bindings_changed = true;

View File

@ -14,7 +14,7 @@
class wxFileHistory; class wxFileHistory;
// Default joystick bindings. // Default joystick bindings.
extern const std::map<config::GameControl, std::set<config::UserInput>> extern const std::map<config::GameControl, std::unordered_set<config::UserInput>>
kDefaultBindings; kDefaultBindings;
extern struct opts_t { extern struct opts_t {
@ -36,7 +36,7 @@ extern struct opts_t {
int rewind_interval = 0; int rewind_interval = 0;
/// Joypad /// Joypad
std::map<config::GameControl, std::set<config::UserInput>> std::map<config::GameControl, std::unordered_set<config::UserInput>>
game_control_bindings; game_control_bindings;
int autofire_rate = 1; int autofire_rate = 1;

View File

@ -208,8 +208,7 @@ std::vector<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index,
if (previous_status != JoyAxisStatus::Neutral) { if (previous_status != JoyAxisStatus::Neutral) {
// Send the "unpressed" event. // Send the "unpressed" event.
events.push_back(UserInputEvent( events.push_back(UserInputEvent(
config::UserInput(index, AxisStatusToJoyControl(previous_status), wx_joystick_), config::JoyInput(wx_joystick_, AxisStatusToJoyControl(previous_status), index), false));
false));
} }
// We already sent the "unpressed" event so nothing more to do. // We already sent the "unpressed" event so nothing more to do.
@ -219,7 +218,7 @@ std::vector<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index,
// Send the "pressed" event. // Send the "pressed" event.
events.push_back(UserInputEvent( events.push_back(UserInputEvent(
config::UserInput(index, AxisStatusToJoyControl(status), wx_joystick_), true)); config::JoyInput(wx_joystick_, AxisStatusToJoyControl(status), index), true));
return events; return events;
} }
@ -238,7 +237,7 @@ std::vector<UserInputEvent> JoyState::ProcessButtonEvent(const uint8_t index, co
// Send the event. // Send the event.
events.push_back( events.push_back(
UserInputEvent(config::UserInput(index, config::JoyControl::Button, wx_joystick_), status)); UserInputEvent(config::JoyInput(wx_joystick_, config::JoyControl::Button, index), status));
return events; return events;
} }
@ -264,12 +263,12 @@ std::vector<UserInputEvent> JoyState::ProcessHatEvent(const uint8_t index, const
if (old_control_pressed && !new_control_pressed) { if (old_control_pressed && !new_control_pressed) {
// Send the "unpressed" event. // Send the "unpressed" event.
events.push_back(UserInputEvent( events.push_back(UserInputEvent(
config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), false)); config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), false));
} }
if (!old_control_pressed && new_control_pressed) { if (!old_control_pressed && new_control_pressed) {
// Send the "pressed" event. // Send the "pressed" event.
events.push_back(UserInputEvent( events.push_back(UserInputEvent(
config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), true)); config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), true));
} }
} }

View File

@ -46,8 +46,9 @@ void UserInputCtrl::SetMultiKey(bool multikey) {
Clear(); Clear();
} }
void UserInputCtrl::SetInputs(const std::set<config::UserInput>& inputs) { void UserInputCtrl::SetInputs(const std::unordered_set<config::UserInput>& inputs) {
inputs_ = inputs; inputs_.clear();
inputs_.insert(inputs.begin(), inputs.end());
UpdateText(); UpdateText();
} }
@ -126,7 +127,7 @@ bool UserInputCtrlValidator::TransferFromWindow() {
UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl); UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl);
assert(control); assert(control);
gopts.game_control_bindings[game_control_] = control->inputs(); gopts.game_control_bindings.insert({game_control_, control->inputs()});
return true; return true;
} }

View File

@ -1,7 +1,7 @@
#ifndef VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_ #ifndef VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_
#define VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_ #define VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_
#include <set> #include <unordered_set>
#include <wx/longlong.h> #include <wx/longlong.h>
#include <wx/string.h> #include <wx/string.h>
@ -46,7 +46,7 @@ public:
void SetMultiKey(bool multikey); void SetMultiKey(bool multikey);
// Sets this control inputs. // Sets this control inputs.
void SetInputs(const std::set<config::UserInput>& inputs); void SetInputs(const std::unordered_set<config::UserInput>& inputs);
// Helper method to return the single input for no multikey UserInputCtrls. // Helper method to return the single input for no multikey UserInputCtrls.
// Asserts if `is_multikey_` is true. // Asserts if `is_multikey_` is true.
@ -54,7 +54,7 @@ public:
config::UserInput SingleInput() const; config::UserInput SingleInput() const;
// Returns the inputs set in this control. // Returns the inputs set in this control.
const std::set<config::UserInput>& inputs() const { return inputs_; } const std::unordered_set<config::UserInput>& inputs() const { return inputs_; }
// Clears the inputs set in this control. // Clears the inputs set in this control.
void Clear() override; void Clear() override;
@ -78,7 +78,7 @@ private:
// subsequent events until the control is focused again. // subsequent events until the control is focused again.
bool is_navigating_away_ = false; bool is_navigating_away_ = false;
std::set<config::UserInput> inputs_; std::unordered_set<config::UserInput> inputs_;
}; };
// A validator for the UserInputCtrl. This validator is used to transfer the // A validator for the UserInputCtrl. This validator is used to transfer the

View File

@ -3,7 +3,6 @@
#include <vector> #include <vector>
#include <wx/event.h> #include <wx/event.h>
#include <wx/timer.h>
#include <wx/window.h> #include <wx/window.h>
#include "wx/config/user-input.h" #include "wx/config/user-input.h"
@ -162,7 +161,7 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
} }
const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_); const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_);
std::vector<config::UserInput> new_inputs; std::vector<config::KeyboardInput> new_inputs;
if (key_pressed == WXK_NONE) { if (key_pressed == WXK_NONE) {
// A new standalone modifier was pressed, send the event. // A new standalone modifier was pressed, send the event.
new_inputs.emplace_back(KeyFromModifier(mod_pressed), mod_pressed); new_inputs.emplace_back(KeyFromModifier(mod_pressed), mod_pressed);
@ -179,7 +178,7 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
} }
} }
for (const config::UserInput& input : new_inputs) { for (const config::KeyboardInput& input : new_inputs) {
wxQueueEvent(window_, new UserInputEvent(input, true)); wxQueueEvent(window_, new UserInputEvent(input, true));
} }
} }
@ -221,7 +220,7 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
return; return;
} }
std::vector<config::UserInput> released_inputs; std::vector<config::KeyboardInput> released_inputs;
if (key_released == WXK_NONE) { if (key_released == WXK_NONE) {
// A standalone modifier was released, send it. // A standalone modifier was released, send it.
released_inputs.emplace_back(KeyFromModifier(mod_released), mod_released); released_inputs.emplace_back(KeyFromModifier(mod_released), mod_released);
@ -232,7 +231,7 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
released_inputs.emplace_back(key, wxMOD_NONE); released_inputs.emplace_back(key, wxMOD_NONE);
} else { } else {
// Check if the key was pressed with the active modifiers. // Check if the key was pressed with the active modifiers.
const config::UserInput input_with_modifiers(key, previous_mods); const config::KeyboardInput input_with_modifiers(key, previous_mods);
auto iter = active_mod_inputs_.find(input_with_modifiers); auto iter = active_mod_inputs_.find(input_with_modifiers);
if (iter == active_mod_inputs_.end()) { if (iter == active_mod_inputs_.end()) {
// The key press event was never sent, so do it now. // The key press event was never sent, so do it now.
@ -252,7 +251,7 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
// Also check for any key that were pressed with the previously active // Also check for any key that were pressed with the previously active
// modifiers and release them. // modifiers and release them.
for (const wxKeyCode active_key : active_keys_) { for (const wxKeyCode active_key : active_keys_) {
const config::UserInput input(active_key, previous_mods); const config::KeyboardInput input(active_key, previous_mods);
auto iter = active_mod_inputs_.find(input); auto iter = active_mod_inputs_.find(input);
if (iter != active_mod_inputs_.end()) { if (iter != active_mod_inputs_.end()) {
active_mod_inputs_.erase(iter); active_mod_inputs_.erase(iter);
@ -261,7 +260,7 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
} }
for (const config::UserInput& input : released_inputs) { for (const config::KeyboardInput& input : released_inputs) {
active_mod_inputs_.erase(input); active_mod_inputs_.erase(input);
wxQueueEvent(window_, new UserInputEvent(input, false)); wxQueueEvent(window_, new UserInputEvent(input, false));
} }

View File

@ -50,7 +50,7 @@ private:
std::unordered_set<wxKeyCode> active_keys_; std::unordered_set<wxKeyCode> active_keys_;
std::unordered_set<wxKeyModifier> active_mods_; std::unordered_set<wxKeyModifier> active_mods_;
std::set<config::UserInput> active_mod_inputs_; std::unordered_set<config::KeyboardInput> active_mod_inputs_;
// The wxWindow this object is attached to. // The wxWindow this object is attached to.
// Must outlive this object. // Must outlive this object.

View File

@ -2,6 +2,7 @@
#define VBAM_WX_WXLOGDEBUG_H_ #define VBAM_WX_WXLOGDEBUG_H_
#include <wx/log.h> #include <wx/log.h>
#include <wx/datetime.h>
// make wxLogDebug work on non-debug builds of Wx, and make it use the console // make wxLogDebug work on non-debug builds of Wx, and make it use the console
// on Windows // on Windows