From 18a0067ca72e5e88cfcdde97d4d241176140532f Mon Sep 17 00:00:00 2001 From: Fabrice de Gans Date: Sun, 13 Aug 2023 14:04:01 -0700 Subject: [PATCH] [Input] Unify command handling This unifies command handling between game and shortcut commands. Both types of commands are now handled in a common manner and the binding configuration is shared. In particular, this prevents assigning the same user input (joypad or keyboard) to a game command or a shortcut command. Bug: #745 --- src/wx/CMakeLists.txt | 15 +- src/wx/cmdevents.cpp | 2 +- src/wx/config/CMakeLists.txt | 0 src/wx/config/bindings.cpp | 249 +++++++ src/wx/config/bindings.h | 105 +++ src/wx/config/command.cpp | 172 +++++ src/wx/config/command.h | 269 ++++++++ src/wx/config/emulated-gamepad.cpp | 125 ++++ src/wx/config/emulated-gamepad.h | 49 ++ src/wx/config/game-control.cpp | 291 --------- src/wx/config/game-control.h | 153 ----- src/wx/config/internal/bindings-internal.cpp | 615 ++++++++++++++++++ src/wx/config/internal/bindings-internal.h | 118 ++++ src/wx/config/internal/shortcuts-internal.cpp | 108 --- src/wx/config/internal/shortcuts-internal.h | 21 - src/wx/config/shortcuts.cpp | 174 ----- src/wx/config/shortcuts.h | 87 --- src/wx/config/user-input.h | 8 +- src/wx/dialogs/accel-config.cpp | 131 ++-- src/wx/dialogs/accel-config.h | 13 +- src/wx/dialogs/joypad-config.cpp | 86 ++- src/wx/dialogs/joypad-config.h | 9 +- src/wx/guiinit.cpp | 4 +- src/wx/opts.cpp | 274 ++------ src/wx/opts.h | 7 - src/wx/panel.cpp | 14 +- src/wx/sys.cpp | 6 +- src/wx/widgets/client-data.h | 33 + src/wx/widgets/user-input-ctrl.cpp | 24 - src/wx/widgets/user-input-ctrl.h | 20 - src/wx/wxvbam.cpp | 25 +- src/wx/wxvbam.h | 68 +- 32 files changed, 2012 insertions(+), 1263 deletions(-) delete mode 100644 src/wx/config/CMakeLists.txt create mode 100644 src/wx/config/bindings.cpp create mode 100644 src/wx/config/bindings.h create mode 100644 src/wx/config/command.cpp create mode 100644 src/wx/config/command.h create mode 100644 src/wx/config/emulated-gamepad.cpp create mode 100644 src/wx/config/emulated-gamepad.h delete mode 100644 src/wx/config/game-control.cpp delete mode 100644 src/wx/config/game-control.h create mode 100644 src/wx/config/internal/bindings-internal.cpp create mode 100644 src/wx/config/internal/bindings-internal.h delete mode 100644 src/wx/config/internal/shortcuts-internal.cpp delete mode 100644 src/wx/config/internal/shortcuts-internal.h delete mode 100644 src/wx/config/shortcuts.cpp delete mode 100644 src/wx/config/shortcuts.h create mode 100644 src/wx/widgets/client-data.h diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index bc6ad734..a234043c 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -13,20 +13,22 @@ set(VBAM_WX_COMMON background-input.cpp background-input.h cmdevents.cpp - config/game-control.cpp - config/game-control.h + config/bindings.cpp + config/bindings.h + config/command.cpp + config/command.h + config/emulated-gamepad.cpp + config/emulated-gamepad.h + config/internal/bindings-internal.cpp + config/internal/bindings-internal.h config/internal/option-internal.cpp config/internal/option-internal.h - config/internal/shortcuts-internal.cpp - config/internal/shortcuts-internal.h config/option-id.h config/option-observer.cpp config/option-observer.h config/option-proxy.h config/option.cpp config/option.h - config/shortcuts.cpp - config/shortcuts.h config/user-input.cpp config/user-input.h dialogs/accel-config.cpp @@ -67,6 +69,7 @@ set(VBAM_WX_COMMON # from external source with minor modifications widgets/checkedlistctrl.cpp widgets/checkedlistctrl.h + widgets/client-data.h widgets/dpi-support.h widgets/group-check-box.cpp widgets/group-check-box.h diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index f643a426..679a4bc2 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -2174,7 +2174,7 @@ EVT_HANDLER(EmulatorDirectories, "Directories...") EVT_HANDLER(JoypadConfigure, "Joypad options...") { if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) { - update_joypad_opts(); + update_shortcut_opts(); } } diff --git a/src/wx/config/CMakeLists.txt b/src/wx/config/CMakeLists.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/wx/config/bindings.cpp b/src/wx/config/bindings.cpp new file mode 100644 index 00000000..d52bc63b --- /dev/null +++ b/src/wx/config/bindings.cpp @@ -0,0 +1,249 @@ +#include "wx/config/bindings.h" + +#include +#include +#include +#include + +#include "wx/config/user-input.h" + +#define VBAM_BINDINGS_INTERNAL_INCLUDE +#include "wx/config/internal/bindings-internal.h" +#undef VBAM_BINDINGS_INTERNAL_INCLUDE + +namespace config { + +namespace { + +int NoopCommand() { + static const int noop = XRCID("NOOP"); + return noop; +} + +} // namespace + +// static +const std::unordered_set& Bindings::DefaultInputsForCommand(const Command& command) { + return internal::DefaultInputsForCommand(command); +} + +Bindings::Bindings() { + // Set up default shortcuts. + for (const auto& iter : internal::DefaultInputs()) { + for (const auto& input : iter.second) { + AssignInputToCommand(input, iter.first); + } + } +} + +Bindings::Bindings( + const std::unordered_map>& control_to_inputs, + const std::unordered_map& input_to_control, + const std::unordered_map& disabled_defaults) + : control_to_inputs_(control_to_inputs.begin(), control_to_inputs.end()), + input_to_control_(input_to_control.begin(), input_to_control.end()), + disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {} + +std::vector> Bindings::GetKeyboardConfiguration() const { + std::vector> config; + config.reserve(control_to_inputs_.size() + 1); + + if (!disabled_defaults_.empty()) { + std::unordered_set noop_inputs; + for (const auto& iter : disabled_defaults_) { + noop_inputs.insert(iter.first); + } + config.push_back(std::make_pair(NoopCommand(), UserInput::SpanToConfigString(noop_inputs))); + } + + for (const auto& iter : control_to_inputs_) { + if (iter.first.is_game()) { + // We only consider shortcut assignments here. + continue; + } + + // Gather the inputs for this command. + std::unordered_set inputs; + for (const auto& input : iter.second) { + if (internal::IsDefaultInputForCommand(iter.first, input)) { + // Default assignments are ignored. + continue; + } + // Not a default input. + inputs.insert(input); + } + + if (!inputs.empty()) { + const int command_id = iter.first.shortcut().id(); + config.push_back(std::make_pair(command_id, UserInput::SpanToConfigString(inputs))); + } + } + + return config; +} + +std::vector> Bindings::GetJoypadConfiguration() const { + std::vector> config; + config.reserve(kNbGameKeys * kNbJoypads); + + for (const auto& game_command : internal::kOrderedGameCommands) { + const auto iter = control_to_inputs_.find(Command(game_command)); + if (iter == control_to_inputs_.end()) { + config.push_back(std::make_pair(game_command, wxEmptyString)); + continue; + } + + const std::unordered_set& inputs = iter->second; + config.push_back(std::make_pair(game_command, UserInput::SpanToConfigString(inputs))); + } + + return config; +} + +std::unordered_set Bindings::InputsForCommand(const Command& command) const { + if (command.is_shortcut() && command.shortcut().id() == NoopCommand()) { + std::unordered_set noop_inputs; + for (const auto& iter : disabled_defaults_) { + noop_inputs.insert(iter.first); + } + return noop_inputs; + } + + auto iter = control_to_inputs_.find(command); + if (iter == control_to_inputs_.end()) { + return {}; + } + return iter->second; +} + +nonstd::optional Bindings::CommandForInput(const UserInput& input) const { + const auto iter = input_to_control_.find(input); + if (iter == input_to_control_.end()) { + return nonstd::nullopt; + } + return iter->second; +} + +Bindings Bindings::Clone() const { + return Bindings(this->control_to_inputs_, this->input_to_control_, this->disabled_defaults_); +} + +void Bindings::AssignInputToCommand(const UserInput& input, const Command& command) { + if (command.is_shortcut() && command.shortcut().id() == NoopCommand()) { + // "Assigning to Noop" means unassinging the default binding. + UnassignDefaultBinding(input); + return; + } + + // Remove the existing binding if it exists. + auto iter = input_to_control_.find(input); + if (iter != input_to_control_.end()) { + UnassignInput(input); + } + + if (command.is_shortcut()) { + const ShortcutCommand& shortcut_command = command.shortcut(); + auto disabled_iter = disabled_defaults_.find(input); + if (disabled_iter != disabled_defaults_.end()) { + const ShortcutCommand& original_command = disabled_iter->second; + if (original_command == shortcut_command) { + // Restoring a disabled input. Remove from the disabled set. + disabled_defaults_.erase(disabled_iter); + } + // Then, just continue normally. + } + } + + control_to_inputs_[command].emplace(input); + input_to_control_.emplace(std::make_pair(input, command)); +} + +void Bindings::AssignInputsToCommand(const std::unordered_set& inputs, + const Command& command) { + // Remove the existing binding if it exists. + const auto iter = control_to_inputs_.find(command); + if (iter != control_to_inputs_.end()) { + // We need to make a copy here because the iterator is going to be invalidated. + const std::unordered_set inputs_to_unassign = iter->second; + for (const UserInput& user_input : inputs_to_unassign) { + UnassignInput(user_input); + } + } + + for (const UserInput& user_input : inputs) { + AssignInputToCommand(user_input, command); + } +} + +void Bindings::UnassignInput(const UserInput& input) { + assert(input); + + auto iter = input_to_control_.find(input); + if (iter == input_to_control_.end()) { + // Input not found, nothing to do. + return; + } + + if (iter->second.is_shortcut()) { + if (internal::IsDefaultInputForCommand(iter->second, input)) { + // Unassigning a default binding has some special handling. + UnassignDefaultBinding(input); + return; + } + } + + // Otherwise, just remove it from the 2 maps. + auto command_iter = control_to_inputs_.find(iter->second); + assert(command_iter != control_to_inputs_.end()); + + command_iter->second.erase(input); + if (command_iter->second.empty()) { + // Remove empty set. + control_to_inputs_.erase(command_iter); + } + input_to_control_.erase(iter); +} + +void Bindings::ClearCommandAssignments(const Command& command) { + auto iter = control_to_inputs_.find(command); + if (iter == control_to_inputs_.end()) { + // Command not found, nothing to do. + return; + } + + for (const UserInput& input : iter->second) { + input_to_control_.erase(input); + } + control_to_inputs_.erase(iter); +} + +void Bindings::UnassignDefaultBinding(const UserInput& input) { + auto input_iter = input_to_control_.find(input); + if (input_iter == input_to_control_.end()) { + // This can happen if the INI file provided by the user has an invalid + // option. In this case, just silently ignore it. + return; + } + + if (!input_iter->second.is_shortcut()) { + return; + } + + if (!internal::IsDefaultInputForCommand(input_iter->second, input)) { + // As above, we have already removed the default binding, ignore it. + return; + } + + auto command_iter = control_to_inputs_.find(input_iter->second); + assert(command_iter != control_to_inputs_.end()); + + command_iter->second.erase(input); + if (command_iter->second.empty()) { + control_to_inputs_.erase(command_iter); + } + + disabled_defaults_.emplace(std::make_pair(input, input_iter->second.shortcut())); + input_to_control_.erase(input_iter); +} + +} // namespace config diff --git a/src/wx/config/bindings.h b/src/wx/config/bindings.h new file mode 100644 index 00000000..57615f69 --- /dev/null +++ b/src/wx/config/bindings.h @@ -0,0 +1,105 @@ +#ifndef VBAM_WX_CONFIG_BINDINGS_H_ +#define VBAM_WX_CONFIG_BINDINGS_H_ + +#include +#include +#include +#include + +#include "wx/config/command.h" +#include "wx/config/user-input.h" + +// wxWidgets only goes up to `wxID_FILE9` but we want 10 recent files. +#define wxID_FILE10 (wxID_FILE9 + 1) + +namespace config { + +// Bindings is a class that manages the association between commands and +// user inputs. It is used to manage the shortcuts configuration. The class +// provides methods to assign and unassign inputs to commands, as well as +// retrieve the current configuration for the INI file. +class Bindings { +public: + // Returns the list of default inputs for `command`. + static const std::unordered_set& DefaultInputsForCommand(const Command& command); + + Bindings(); + ~Bindings() = default; + + Bindings(Bindings&&) noexcept = default; + Bindings& operator=(Bindings&&) noexcept = default; + + // Disable copy and copy assignment operator. + // `Clone()` is provided only for the configuration window, this class + // should otherwise be treated as move-only. If you wish to access the + // Bindings configuration, do it from `wxGetApp().bindings()`. + Bindings(const Bindings&) = delete; + Bindings& operator=(const Bindings&) = delete; + + // Returns the shortcuts configuration for the INI file. + // Internally, there are global default system inputs that are immediately + // available on first run. For the configuration saved in the [Keyboard] + // section of the vbam.ini file, we only keep track of the following: + // - Disabled default input. These appear under [Keyboard/NOOP]. + // - User-added custom bindings. These appear under [Keyboard/CommandName]. + // Essentially, this is a diff between the default shortcuts and the user + // configuration. + std::vector> GetKeyboardConfiguration() const; + + // Returns the game control configuration for the INI file. These go in the + // [Joypad] section of the INI file. + std::vector> GetJoypadConfiguration() const; + + // Returns the list of input currently configured for `command`. + std::unordered_set InputsForCommand(const Command& command) const; + + // Returns the Command currently assigned to `input` or nullopt if none. + nonstd::optional CommandForInput(const UserInput& input) const; + + // Returns a copy of this object. This can be an expensive operation and + // should only be used to modify the currently active shortcuts + // configuration. + Bindings Clone() const; + + // Assigns `input` to `command`. Silently unassigns `input` if it is already + // assigned to another command. + void AssignInputToCommand(const UserInput& input, const Command& command); + + // Assigns `inputs` to `command`. Silently unassigns any of `inputs` if they + // are already assigned to another command. Any input previously assigned to + // `command` will be cleared. + void AssignInputsToCommand(const std::unordered_set& inputs, const Command& command); + + // Removes `input` assignment. No-op if `input` is not assigned. `input` + // must be a valid UserInput. Call will assert otherwise. Call will assert otherwise. + void UnassignInput(const UserInput& input); + + // Removes all assignments for `command`. No-op if `command` has no assignment. + void ClearCommandAssignments(const Command& command); + +private: + // Faster constructor for explicit copy. + Bindings( + const std::unordered_map>& control_to_inputs, + const std::unordered_map& input_to_control, + const std::unordered_map& disabled_defaults); + + // Helper method to unassign a binding used by the default configuration. + // This requires special handling since the INI configuration is a diff + // between the default bindings and the user configuration. + void UnassignDefaultBinding(const UserInput& input); + + // Map of command to their associated input set. + std::unordered_map> control_to_inputs_; + // Reverse map of the above. An input can only map to a single command. + std::unordered_map input_to_control_; + // Disabled default shortcuts. This is used to easily retrieve the + // configuration to save in the INI file. + std::unordered_map disabled_defaults_; +}; + +using BindingsProvider = std::function; + +} // namespace config + +#endif // VBAM_WX_CONFIG_BINDINGS_H_ diff --git a/src/wx/config/command.cpp b/src/wx/config/command.cpp new file mode 100644 index 00000000..5339bb17 --- /dev/null +++ b/src/wx/config/command.cpp @@ -0,0 +1,172 @@ +#include "wx/config/command.h" + +#include + +#include + +#include "wx/strutils.h" +#include "wx/wxvbam.h" + +namespace config { +namespace { + +constexpr int GameKeyToInt(const GameKey& game_key) { + return static_cast(game_key); +} + +// Returns true if `joypad` is in a valid joypad range. +constexpr bool JoypadInRange(const int& joypad) { + constexpr size_t kMinJoypadIndex = 0; + return static_cast(joypad) >= kMinJoypadIndex && + static_cast(joypad) < kNbJoypads; +} + +wxString GameKeyToUxString(const GameKey& game_key) { + // Note: this must match GUI widget names or GUI won't work + // This array's order determines tab order as well + static const std::array kGameKeyStrings = { + _("Up"), _("Down"), _("Left"), _("Right"), _("A"), + _("B"), _("L"), _("R"), _("Select"), _("Start"), + _("Motion Up"), _("Motion Down"), _("Motion Left"), _("Motion Right"), _("Motion In"), + _("Motion Out"), _("Auto A"), _("Auto B"), _("Speed"), _("Capture"), + _("GameShark"), + }; + return kGameKeyStrings[GameKeyToInt(game_key)]; +} + +} // namespace + +// clang-format off +wxString GameKeyToString(const GameKey& game_key) { + // Note: this must match GUI widget names or GUI won't work + // This array's order determines tab order as well + static const std::array kGameKeyStrings = { + "Up", + "Down", + "Left", + "Right", + "A", + "B", + "L", + "R", + "Select", + "Start", + "MotionUp", + "MotionDown", + "MotionLeft", + "MotionRight", + "MotionIn", + "MotionOut", + "AutoA", + "AutoB", + "Speed", + "Capture", + "GS", + }; + return kGameKeyStrings[GameKeyToInt(game_key)]; +} + +nonstd::optional StringToGameKey(const wxString& input) { + static const std::map kStringToGameKey = { + { "Up", GameKey::Up }, + { "Down", GameKey::Down }, + { "Left", GameKey::Left }, + { "Right", GameKey::Right }, + { "A", GameKey::A }, + { "B", GameKey::B }, + { "L", GameKey::L }, + { "R", GameKey::R }, + { "Select", GameKey::Select }, + { "Start", GameKey::Start }, + { "MotionUp", GameKey::MotionUp }, + { "MotionDown", GameKey::MotionDown }, + { "MotionLeft", GameKey::MotionLeft }, + { "MotionRight", GameKey::MotionRight }, + { "MotionIn", GameKey::MotionIn }, + { "MotionOut", GameKey::MotionOut }, + { "AutoA", GameKey::AutoA }, + { "AutoB", GameKey::AutoB }, + { "Speed", GameKey::Speed }, + { "Capture", GameKey::Capture }, + { "GS", GameKey::Gameshark }, + }; + + const auto iter = kStringToGameKey.find(input); + if (iter == kStringToGameKey.end()) { + return nonstd::nullopt; + } + return iter->second; +} +// clang-format on + +wxString GameCommand::ToConfigString() const { + return wxString::Format("Joypad/%zu/%s", joypad_.ux_index(), GameKeyToString(game_key_)); +} +wxString GameCommand::ToUXString() const { + return wxString::Format(_("Joypad %zu %s"), joypad_.ux_index(), GameKeyToUxString(game_key())); +} + +wxString ShortcutCommand::ToConfigString() const { + int cmd = 0; + for (cmd = 0; cmd < ncmds; cmd++) + if (cmdtab[cmd].cmd_id == id_) + break; + if (cmd == ncmds) { + // Command not found. This should never happen. + assert(false); + return wxEmptyString; + } + + return wxString::Format("Keyboard/%s", cmdtab[cmd].cmd); +} + +// static +nonstd::optional Command::FromString(const wxString& name) { + static const wxString kKeyboard("Keyboard"); + static const wxString kJoypad("Joypad"); + + bool is_keyboard = !wxStrncmp(name, kKeyboard, kKeyboard.size()); + bool is_joypad = !wxStrncmp(name, kJoypad, kJoypad.size()); + if (!is_keyboard && !is_joypad) { + wxLogDebug("Doesn't start with joypad or keyboard"); + return nonstd::nullopt; + } + + auto parts = strutils::split(name, "/"); + if (is_joypad) { + if (parts.size() != 3) { + wxLogDebug("Wrong split size: %d", parts.size()); + return nonstd::nullopt; + } + + const int joypad = parts[1][0] - '1'; + if (!JoypadInRange(joypad)) { + wxLogDebug("Wrong joypad index: %d", joypad); + return nonstd::nullopt; + } + + const nonstd::optional game_key = StringToGameKey(parts[2]); + if (!game_key) { + wxLogDebug("Failed to parse game_key: %s", parts[2]); + return nonstd::nullopt; + } + + return Command(GameCommand(GameJoy(joypad), *game_key)); + } else { + if (parts.size() != 2) { + wxLogDebug("Wrong split size: %d", parts.size()); + return nonstd::nullopt; + } + + const cmditem* cmd = std::lower_bound( + &cmdtab[0], &cmdtab[ncmds], cmditem{parts[1], wxString(), 0, 0, NULL}, cmditem_lt); + if (cmd == &cmdtab[ncmds] || wxStrcmp(parts[1], cmd->cmd)) { + wxLogDebug("Command ID %s not found", parts[1]); + return nonstd::nullopt; + } + + return Command(ShortcutCommand(cmd->cmd_id)); + } +} + +} // namespace config diff --git a/src/wx/config/command.h b/src/wx/config/command.h new file mode 100644 index 00000000..118c3642 --- /dev/null +++ b/src/wx/config/command.h @@ -0,0 +1,269 @@ +#ifndef VBAM_WX_CONFIG_COMMAND_H_ +#define VBAM_WX_CONFIG_COMMAND_H_ + +#include +#include +#include + +#include +#include + +#include + +namespace config { + +// clang-format off +// Represents an in-game input. +enum class GameKey { + Up = 0, + Down, + Left, + Right, + A, + B, + L, + R, + Select, + Start, + MotionUp, + MotionDown, + MotionLeft, + MotionRight, + MotionIn, + MotionOut, + AutoA, + AutoB, + Speed, + Capture, + Gameshark, + Last = Gameshark +}; +static constexpr size_t kNbGameKeys = static_cast(GameKey::Last) + 1; + +static constexpr std::array kAllGameKeys = { + GameKey::Up, GameKey::Down, GameKey::Left, + GameKey::Right, GameKey::A, GameKey::B, + GameKey::L, GameKey::R, GameKey::Select, + GameKey::Start, GameKey::MotionUp, GameKey::MotionDown, + GameKey::MotionLeft, GameKey::MotionRight, GameKey::MotionIn, + GameKey::MotionOut, GameKey::AutoA, GameKey::AutoB, + GameKey::Speed, GameKey::Capture, GameKey::Gameshark, +}; +// clang-format on + +static constexpr size_t kNbJoypads = 4; + +// Represents an emulated joypad. The internal index is zero-based. +class GameJoy { +public: + constexpr explicit GameJoy(size_t index) : index_(index) { assert(index < kNbJoypads); } + + // The underlying zero-based index for this emulated joypad. + constexpr size_t index() const { return index_; } + + // For display and INI purposes, the index is one-based. + constexpr size_t ux_index() const { return index_ + 1; } + + constexpr bool operator==(const GameJoy& other) const { return index_ == other.index_; } + constexpr bool operator!=(const GameJoy& other) const { return !(*this == other); } + constexpr bool operator<(const GameJoy& other) const { return index_ < other.index_; } + constexpr bool operator<=(const GameJoy& other) const { + return *this < other || *this == other; + } + constexpr bool operator>(const GameJoy& other) const { return !(*this <= other); } + constexpr bool operator>=(const GameJoy& other) const { return !(*this < other); } + +private: + const size_t index_; +}; + +static constexpr std::array kAllGameJoys = { + GameJoy(0), + GameJoy(1), + GameJoy(2), + GameJoy(3), +}; + +// A Game Command is represented by a `joypad` number (1 to 4) and a +// `game_key`. `joypad` MUST be in the 1-4 range. Debug checks will assert +// otherwise. +class GameCommand final { +public: + constexpr GameCommand(GameJoy joypad, GameKey game_key) + : joypad_(joypad), game_key_(game_key) {} + + constexpr GameJoy joypad() const { return joypad_; } + constexpr GameKey game_key() const { return game_key_; } + wxString ToConfigString() const; + wxString ToUXString() const; + + constexpr bool operator==(const GameCommand& other) const { + return joypad_ == other.joypad_ && game_key_ == other.game_key_; + } + constexpr bool operator!=(const GameCommand& other) const { return !(*this == other); } + constexpr bool operator<(const GameCommand& other) const { + if (joypad_ == other.joypad_) { + return game_key_ < other.game_key_; + } else { + return joypad_ < other.joypad_; + } + } + constexpr bool operator<=(const GameCommand& other) const { + return *this < other || *this == other; + } + constexpr bool operator>(const GameCommand& other) const { return !(*this <= other); } + constexpr bool operator>=(const GameCommand& other) const { return !(*this < other); } + +private: + const GameJoy joypad_; + const GameKey game_key_; +}; + +// A Shortcut Command is represented by the wx command ID. +class ShortcutCommand final { +public: + constexpr explicit ShortcutCommand(int id) : id_(id) {} + + constexpr int id() const { return id_; } + wxString ToConfigString() const; + + constexpr bool operator==(const ShortcutCommand& other) const { return id_ == other.id_; } + constexpr bool operator!=(const ShortcutCommand& other) const { return !(*this == other); } + constexpr bool operator<(const ShortcutCommand& other) const { return id_ < other.id_; } + constexpr bool operator<=(const ShortcutCommand& other) const { + return *this < other || *this == other; + } + constexpr bool operator>(const ShortcutCommand& other) const { return !(*this <= other); } + constexpr bool operator>=(const ShortcutCommand& other) const { return !(*this < other); } + +private: + const int id_; +}; + +// Conversion utility method. Returns empty string on failure. +// This is O(1). +wxString GameKeyToString(const GameKey& game_key); + +// Conversion utility method. Returns std::nullopt on failure. +// This is O(log(kNbGameKeys)). +nonstd::optional StringToGameKey(const wxString& input); + +// Represents a Command for the emulator software. This can be either a Game +// Command (a button is pressed on the emulated device) or a Shortcut Command +// (a user-specified shortcut is activated). +// This is mainly used by the Bindings class to handle assignment in a common +// manner and prevent the user from assigning the same input to multiple +// Commands, Game or Shortcut. +class Command final { +public: + enum class Tag { + kGame = 0, + kShortcut, + }; + + // Converts a string to a Command. Returns std::nullopt on failure. + static nonstd::optional FromString(const wxString& name); + + // Game Command constructor. + Command(GameCommand game_control) : tag_(Tag::kGame), control_(game_control) {} + + // Shortcut Command constructors. + Command(ShortcutCommand shortcut_control) : tag_(Tag::kShortcut), control_(shortcut_control) {} + + ~Command() = default; + + // Returns the type of the value stored by the current object. + Tag tag() const { return tag_; } + + bool is_game() const { return tag() == Tag::kGame; } + bool is_shortcut() const { return tag() == Tag::kShortcut; } + + const GameCommand& game() const { + assert(is_game()); + return nonstd::get(control_); + } + + const ShortcutCommand& shortcut() const { + assert(is_shortcut()); + return nonstd::get(control_); + } + + bool operator==(const Command& other) const { + return tag_ == other.tag_ && control_ == other.control_; + } + bool operator!=(const Command& other) const { return !(*this == other); } + bool operator<(const Command& other) const { + if (tag_ == other.tag_) { + switch (tag_) { + case Tag::kGame: + return game() < other.game(); + case Tag::kShortcut: + return shortcut() < other.shortcut(); + } + + // Unreachable. + assert(false); + return false; + } else { + return tag_ < other.tag_; + } + } + bool operator<=(const Command& other) const { return *this < other || *this == other; } + bool operator>(const Command& other) const { return !(*this <= other); } + bool operator>=(const Command& other) const { return !(*this < other); } + +private: + const Tag tag_; + const nonstd::variant control_; +}; + +} // namespace config + +// Specializations for hash functions for all of the above classes. +template <> +struct std::hash { + std::size_t operator()(const config::GameKey& game_key) const noexcept { + return std::hash{}(static_cast(game_key)); + } +}; + +template <> +struct std::hash { + std::size_t operator()(const config::GameJoy& game_joy) const noexcept { + return std::hash{}(static_cast(game_joy.index())); + } +}; + +template <> +struct std::hash { + std::size_t operator()(const config::GameCommand& game_control) const noexcept { + const std::size_t hash1 = std::hash{}(game_control.joypad()); + const std::size_t hash2 = std::hash{}(game_control.game_key()); + return hash1 ^ hash2; + } +}; + +template <> +struct std::hash { + std::size_t operator()(const config::ShortcutCommand& shortcut) const noexcept { + return std::hash{}(shortcut.id()); + } +}; + +template <> +struct std::hash { + std::size_t operator()(const config::Command& control) const noexcept { + switch (control.tag()) { + case config::Command::Tag::kGame: + return std::hash{}(control.game()); + case config::Command::Tag::kShortcut: + return std::hash{}(control.shortcut()); + } + + // Unreachable. + assert(false); + return 0; + } +}; + +#endif // VBAM_WX_CONFIG_COMMAND_H_ diff --git a/src/wx/config/emulated-gamepad.cpp b/src/wx/config/emulated-gamepad.cpp new file mode 100644 index 00000000..1215a1f8 --- /dev/null +++ b/src/wx/config/emulated-gamepad.cpp @@ -0,0 +1,125 @@ +#include "wx/config/emulated-gamepad.h" + +namespace config { + +namespace { + +constexpr uint32_t kBitKeyA = (1 << 0); +constexpr uint32_t kBitKeyB = (1 << 1); +constexpr uint32_t kBitKeySelect = (1 << 2); +constexpr uint32_t kBitKeyStart = (1 << 3); +constexpr uint32_t kBitKeyRight = (1 << 4); +constexpr uint32_t kBitKeyLeft = (1 << 5); +constexpr uint32_t kBitKeyUp = (1 << 6); +constexpr uint32_t kBitKeyDown = (1 << 7); +constexpr uint32_t kBitKeyR = (1 << 8); +constexpr uint32_t kBitKeyL = (1 << 9); +constexpr uint32_t kBitKeySpeed = (1 << 10); +constexpr uint32_t kBitKeyCapture = (1 << 11); +constexpr uint32_t kBitKeyGameShark = (1 << 12); +constexpr uint32_t kBitKeyAutoA = (1 << 13); +constexpr uint32_t kBitKeyAutoB = (1 << 14); +constexpr uint32_t kBitKeyMotionUp = (1 << 15); +constexpr uint32_t kBitKeyMotionDown = (1 << 16); +constexpr uint32_t kBitKeyMotionLeft = (1 << 17); +constexpr uint32_t kBitKeyMotionRight = (1 << 18); +constexpr uint32_t kBitKeyMotionIn = (1 << 19); +constexpr uint32_t kBitKeyMotionOut = (1 << 20); + +// clang-format off +constexpr std::array kBitMask = { + kBitKeyUp, + kBitKeyDown, + kBitKeyLeft, + kBitKeyRight, + kBitKeyA, + kBitKeyB, + kBitKeyL, + kBitKeyR, + kBitKeySelect, + kBitKeyStart, + kBitKeyMotionUp, + kBitKeyMotionDown, + kBitKeyMotionLeft, + kBitKeyMotionRight, + kBitKeyMotionIn, + kBitKeyMotionOut, + kBitKeyAutoA, + kBitKeyAutoB, + kBitKeySpeed, + kBitKeyCapture, + kBitKeyGameShark, +}; +// clang-format on + +inline size_t GameKeyToInt(const GameKey& game_key) { + return static_cast(game_key); +} + +} // namespace + +EmulatedGamepad::EmulatedGamepad(const BindingsProvider bindings_provider) + : joypads_({0, 0, 0, 0}), bindings_provider_(bindings_provider) {} + +bool EmulatedGamepad::OnInputPressed(const config::UserInput& user_input) { + assert(user_input); + + const auto command = bindings_provider_()->CommandForInput(user_input); + if (!command || !command->is_game()) { + // No associated game control for `user_input`. + return false; + } + + // Update the corresponding control. + auto iter = active_controls_.find(command->game()); + if (iter == active_controls_.end()) { + iter = active_controls_ + .insert(std::make_pair(command->game(), std::unordered_set())) + .first; + } + + iter->second.emplace(user_input); + joypads_[command->game().joypad().index()] |= + kBitMask[GameKeyToInt(command->game().game_key())]; + return true; +} + +bool EmulatedGamepad::OnInputReleased(const config::UserInput& user_input) { + assert(user_input); + + const auto command = bindings_provider_()->CommandForInput(user_input); + if (!command || !command->is_game()) { + // No associated game control for `user_input`. + return false; + } + + // Update the corresponding control. + auto iter = active_controls_.find(command->game()); + if (iter == active_controls_.end()) { + // Double release is noop. + return true; + } + + iter->second.erase(user_input); + if (iter->second.empty()) { + // Actually release control. + active_controls_.erase(iter); + joypads_[command->game().joypad().index()] &= + ~kBitMask[GameKeyToInt(command->game().game_key())]; + } + return true; +} + +void EmulatedGamepad::Reset() { + active_controls_.clear(); + joypads_.fill(0); +} + +uint32_t EmulatedGamepad::GetJoypad(size_t joypad) const { + if (joypad >= kNbJoypads) { + return 0; + } + return joypads_[joypad]; +} + +} // namespace config diff --git a/src/wx/config/emulated-gamepad.h b/src/wx/config/emulated-gamepad.h new file mode 100644 index 00000000..5a70f6a1 --- /dev/null +++ b/src/wx/config/emulated-gamepad.h @@ -0,0 +1,49 @@ +#ifndef VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_ +#define VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_ + +#include +#include +#include +#include + +#include + +#include "wx/config/bindings.h" +#include "wx/config/command.h" +#include "wx/config/user-input.h" + +namespace config { + +// Tracks in-game input and computes the joypad value used to send control input +// data to the emulator. This class should be kept as a singleton owned by the +// application. +class EmulatedGamepad final { +public: + explicit EmulatedGamepad(const BindingsProvider bindings_provider); + ~EmulatedGamepad() = default; + + // Disable copy constructor and assignment operator. + EmulatedGamepad(const EmulatedGamepad&) = delete; + EmulatedGamepad& operator=(const EmulatedGamepad&) = delete; + + // Processes `user_input` and updates the internal tracking state. + // Returns true if `user_input` corresponds to a game input. + bool OnInputPressed(const UserInput& user_input); + bool OnInputReleased(const UserInput& user_input); + + // Clears all input. + void Reset(); + + uint32_t GetJoypad(size_t joypad) const; + +private: + std::unordered_map> active_controls_; + std::array joypads_; + const BindingsProvider bindings_provider_; +}; + +using EmulatedGamepadProvider = std::function; + +} // namespace config + +#endif // VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_ diff --git a/src/wx/config/game-control.cpp b/src/wx/config/game-control.cpp deleted file mode 100644 index ff524593..00000000 --- a/src/wx/config/game-control.cpp +++ /dev/null @@ -1,291 +0,0 @@ -#include "wx/config/game-control.h" - -#include "wx/strutils.h" -#include "wx/wxlogdebug.h" - -namespace config { - -namespace { - -constexpr uint32_t kBitKeyA = (1 << 0); -constexpr uint32_t kBitKeyB = (1 << 1); -constexpr uint32_t kBitKeySelect = (1 << 2); -constexpr uint32_t kBitKeyStart = (1 << 3); -constexpr uint32_t kBitKeyRight = (1 << 4); -constexpr uint32_t kBitKeyLeft = (1 << 5); -constexpr uint32_t kBitKeyUp = (1 << 6); -constexpr uint32_t kBitKeyDown = (1 << 7); -constexpr uint32_t kBitKeyR = (1 << 8); -constexpr uint32_t kBitKeyL = (1 << 9); -constexpr uint32_t kBitKeySpeed = (1 << 10); -constexpr uint32_t kBitKeyCapture = (1 << 11); -constexpr uint32_t kBitKeyGameShark = (1 << 12); -constexpr uint32_t kBitKeyAutoA = (1 << 13); -constexpr uint32_t kBitKeyAutoB = (1 << 14); -constexpr uint32_t kBitKeyMotionUp = (1 << 15); -constexpr uint32_t kBitKeyMotionDown = (1 << 16); -constexpr uint32_t kBitKeyMotionLeft = (1 << 17); -constexpr uint32_t kBitKeyMotionRight = (1 << 18); -constexpr uint32_t kBitKeyMotionIn = (1 << 19); -constexpr uint32_t kBitKeyMotionOut = (1 << 20); - -// clang-format off -constexpr std::array kBitMask = { - kBitKeyUp, - kBitKeyDown, - kBitKeyLeft, - kBitKeyRight, - kBitKeyA, - kBitKeyB, - kBitKeyL, - kBitKeyR, - kBitKeySelect, - kBitKeyStart, - kBitKeyMotionUp, - kBitKeyMotionDown, - kBitKeyMotionLeft, - kBitKeyMotionRight, - kBitKeyMotionIn, - kBitKeyMotionOut, - kBitKeyAutoA, - kBitKeyAutoB, - kBitKeySpeed, - kBitKeyCapture, - kBitKeyGameShark, -}; -// clang-format on - -inline int GameKeyToInt(const GameKey& game_key) { - return static_cast(game_key); -} - -// Returns true if `joypad` is in a valid joypad range. -inline bool JoypadInRange(const int& joypad) { - constexpr int kMinJoypadIndex = 0; - return joypad >= kMinJoypadIndex && joypad < kNbJoypads; -} - -} // namespace - -// clang-format off -wxString GameKeyToString(const GameKey& game_key) { - // Note: this must match GUI widget names or GUI won't work - // This array's order determines tab order as well - static const std::array kGameKeyStrings = { - "Up", - "Down", - "Left", - "Right", - "A", - "B", - "L", - "R", - "Select", - "Start", - "MotionUp", - "MotionDown", - "MotionLeft", - "MotionRight", - "MotionIn", - "MotionOut", - "AutoA", - "AutoB", - "Speed", - "Capture", - "GS", - }; - return kGameKeyStrings[GameKeyToInt(game_key)]; -} - -nonstd::optional StringToGameKey(const wxString& input) { - static const std::map kStringToGameKey = { - { "Up", GameKey::Up }, - { "Down", GameKey::Down }, - { "Left", GameKey::Left }, - { "Right", GameKey::Right }, - { "A", GameKey::A }, - { "B", GameKey::B }, - { "L", GameKey::L }, - { "R", GameKey::R }, - { "Select", GameKey::Select }, - { "Start", GameKey::Start }, - { "MotionUp", GameKey::MotionUp }, - { "MotionDown", GameKey::MotionDown }, - { "MotionLeft", GameKey::MotionLeft }, - { "MotionRight", GameKey::MotionRight }, - { "MotionIn", GameKey::MotionIn }, - { "MotionOut", GameKey::MotionOut }, - { "AutoA", GameKey::AutoA }, - { "AutoB", GameKey::AutoB }, - { "Speed", GameKey::Speed }, - { "Capture", GameKey::Capture }, - { "GS", GameKey::Gameshark }, - }; - - const auto iter = kStringToGameKey.find(input); - if (iter == kStringToGameKey.end()) { - return nonstd::nullopt; - } - return iter->second; -} -// clang-format on - -// static -nonstd::optional GameControl::FromString(const wxString& name) { - static const wxString kJoypad("Joypad"); - if (!wxStrncmp(name, kJoypad, kJoypad.size())) { - wxLogDebug("Doesn't start with joypad"); - return nonstd::nullopt; - } - - auto parts = strutils::split(name, "/"); - if (parts.size() != 3) { - wxLogDebug("Wrong split size: %d", parts.size()); - return nonstd::nullopt; - } - - const int joypad = parts[1][0] - wxT('1'); - if (!JoypadInRange(joypad)) { - wxLogDebug("Wrong joypad index: %d", joypad); - return nonstd::nullopt; - } - - nonstd::optional game_key = StringToGameKey(parts[2]); - if (!game_key) { - wxLogDebug("Failed to parse game_key: %s", parts[2]); - return nonstd::nullopt; - } - - return GameControl(joypad, game_key.value()); -} - -GameControl::GameControl(int joypad, GameKey game_key) - : joypad_(joypad), - game_key_(game_key), - config_string_(wxString::Format("Joypad/%d/%s", - joypad_ + 1, - GameKeyToString(game_key_))) { - assert(JoypadInRange(joypad_)); -} -GameControl::~GameControl() = default; - -bool GameControl::operator==(const GameControl& other) const { - return joypad_ == other.joypad_ && game_key_ == other.game_key_; -} -bool GameControl::operator!=(const GameControl& other) const { - return !(*this == other); -} -bool GameControl::operator<(const GameControl& other) const { - if (joypad_ != other.joypad_) { - return joypad_ < other.joypad_; - } - if (game_key_ != other.game_key_) { - return game_key_ < other.game_key_; - } - return false; -} -bool GameControl::operator<=(const GameControl& other) const { - return !(*this > other); -} -bool GameControl::operator>(const GameControl& other) const { - return other < *this; -} -bool GameControl::operator>=(const GameControl& other) const { - return !(*this < other); -} - -GameControlState::GameControlState(const GameControlBindingsProvider bindings_provider) - : joypads_({0, 0, 0, 0}), bindings_provider_(bindings_provider) {} - -bool GameControlState::OnInputPressed(const config::UserInput& user_input) { - assert(user_input); - - const auto& game_keys = input_bindings_.find(user_input); - if (game_keys == input_bindings_.end()) { - // No associated game control for `user_input`. - return false; - } - - auto iter = keys_pressed_.find(user_input); - if (iter != keys_pressed_.end()) { - // Double press is noop. - return true; - } - - // Remember the key pressed. - keys_pressed_.emplace(user_input); - - // Update all corresponding controls. - for (const GameControl& game_control : game_keys->second) { - active_controls_[game_control].emplace(user_input); - joypads_[game_control.joypad_] |= - kBitMask[GameKeyToInt(game_control.game_key_)]; - } - - return true; -} - -bool GameControlState::OnInputReleased(const config::UserInput& user_input) { - assert(user_input); - - const auto& game_keys = input_bindings_.find(user_input); - if (game_keys == input_bindings_.end()) { - // No associated game control for `user_input`. - return false; - } - - auto iter = keys_pressed_.find(user_input); - if (iter == keys_pressed_.end()) { - // Double release is noop. - return true; - } - - // Release the key pressed. - keys_pressed_.erase(iter); - - // Update all corresponding controls. - for (const GameControl& game_control : game_keys->second) { - auto active_controls = active_controls_.find(game_control); - if (active_controls == active_controls_.end()) { - // This should never happen. - assert(false); - return true; - } - - active_controls->second.erase(user_input); - if (active_controls->second.empty()) { - // Actually release control. - active_controls_.erase(active_controls); - joypads_[game_control.joypad_] &= - ~kBitMask[GameKeyToInt(game_control.game_key_)]; - } - } - - return true; -} - -void GameControlState::Reset() { - active_controls_.clear(); - keys_pressed_.clear(); - joypads_.fill(0); -} - -void GameControlState::OnGameBindingsChanged() { - // We should reset to ensure no key remains accidentally pressed following a - // configuration change. - Reset(); - - input_bindings_.clear(); - for (const auto& iter : *bindings_provider_()) { - for (const auto& user_input : iter.second) { - input_bindings_[user_input].emplace(iter.first); - } - } -} - -uint32_t GameControlState::GetJoypad(int joypad) const { - assert(JoypadInRange(joypad)); - return joypads_[joypad]; -} - -} // namespace config diff --git a/src/wx/config/game-control.h b/src/wx/config/game-control.h deleted file mode 100644 index 32350b3a..00000000 --- a/src/wx/config/game-control.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_ -#define VBAM_WX_CONFIG_GAME_CONTROL_H_ - -#include -#include -#include -#include -#include - -#include - -#include - -#include "wx/config/user-input.h" - -namespace config { - -// Forward declaration. -class GameControlState; - -//clang-format off -// Represents an in-game input. -enum class GameKey { - Up = 0, - Down, - Left, - Right, - A, - B, - L, - R, - Select, - Start, - MotionUp, - MotionDown, - MotionLeft, - MotionRight, - MotionIn, - MotionOut, - AutoA, - AutoB, - Speed, - Capture, - Gameshark, - Last = Gameshark -}; - -inline constexpr int kNbGameKeys = static_cast(GameKey::Last) + 1; -inline constexpr int kNbJoypads = 4; - -inline constexpr std::array kAllGameKeys = { - GameKey::Up, - GameKey::Down, - GameKey::Left, - GameKey::Right, - GameKey::A, - GameKey::B, - GameKey::L, - GameKey::R, - GameKey::Select, - GameKey::Start, - GameKey::MotionUp, - GameKey::MotionDown, - GameKey::MotionLeft, - GameKey::MotionRight, - GameKey::MotionIn, - GameKey::MotionOut, - GameKey::AutoA, - GameKey::AutoB, - GameKey::Speed, - GameKey::Capture, - GameKey::Gameshark, -}; -//clang-format on - -// Conversion utility method. Returns empty string on failure. -// This is O(1). -wxString GameKeyToString(const GameKey& game_key); - -// Conversion utility method. Returns std::nullopt on failure. -// This is O(log(kNbGameKeys)). -nonstd::optional StringToGameKey(const wxString& input); - -// Abstraction for an in-game control, wich is made of a player index (from 0 -// to 3), and a GameKey. -class GameControl { -public: - // Converts a string to a GameControl. Returns std::nullopt on failure. - static nonstd::optional FromString(const wxString& name); - - GameControl(int joypad, GameKey game_key); - ~GameControl(); - - wxString ToString() const { return config_string_; }; - - bool operator==(const GameControl& other) const; - bool operator!=(const GameControl& other) const; - bool operator<(const GameControl& other) const; - bool operator<=(const GameControl& other) const; - bool operator>(const GameControl& other) const; - bool operator>=(const GameControl& other) const; - -private: - const int joypad_; - const GameKey game_key_; - const wxString config_string_; - - friend class GameControlState; -}; - -using GameControlBindings = std::map>; -using GameControlBindingsProvider = std::function; - -// Tracks in-game input and computes the joypad value used to send control input -// data to the emulator. This class should be kept as a singleton owned by the -// application. -class GameControlState final { -public: - explicit GameControlState(const GameControlBindingsProvider bindings_provider); - ~GameControlState() = default; - - // Disable copy constructor and assignment operator. - GameControlState(const GameControlState&) = delete; - GameControlState& operator=(const GameControlState&) = delete; - - // Processes `user_input` and updates the internal tracking state. - // Returns true if `user_input` corresponds to a game input. - bool OnInputPressed(const UserInput& user_input); - bool OnInputReleased(const UserInput& user_input); - - // Clears all input. - void Reset(); - - // Recomputes internal bindinds. This is a potentially slow operation and - // should only be called when the game input configuration has been changed. - void OnGameBindingsChanged(); - - uint32_t GetJoypad(int joypad) const; - -private: - std::map> input_bindings_; - std::map> active_controls_; - std::unordered_set keys_pressed_; - std::array joypads_; - - const GameControlBindingsProvider bindings_provider_; -}; - -using GameControlStateProvider = std::function; - -} // namespace config - -#endif // VBAM_WX_CONFIG_GAME_CONTROL_H_ diff --git a/src/wx/config/internal/bindings-internal.cpp b/src/wx/config/internal/bindings-internal.cpp new file mode 100644 index 00000000..af041f15 --- /dev/null +++ b/src/wx/config/internal/bindings-internal.cpp @@ -0,0 +1,615 @@ +#include "wx/config/bindings.h" +#include "wx/config/command.h" + +#include + +#include + +#define VBAM_BINDINGS_INTERNAL_INCLUDE +#include "wx/config/internal/bindings-internal.h" +#undef VBAM_BINDINGS_INTERNAL_INCLUDE + +namespace config { +namespace internal { + +const std::unordered_map>& DefaultInputs() { + // clang-format off + static const std::unordered_map> kDefaultInputs = { + {ShortcutCommand(XRCID("CheatsList")), + { + KeyboardInput('C', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("NextFrame")), + { + KeyboardInput('N', wxMOD_CMD) + }}, + // this was annoying people A LOT #334 + // {ShortcutCommand(wxID_EXIT), + // { + // KeyboardInput(WXK_ESCAPE, wxMOD_NONE) + // }}, + // this was annoying people #298 + // {ShortcutCommand(wxID_EXIT), + // { + // KeyboardInput('X', wxMOD_CMD) + // }}, + {ShortcutCommand(wxID_EXIT), + { + KeyboardInput('Q', wxMOD_CMD) + }}, + {ShortcutCommand(wxID_CLOSE), + { + KeyboardInput('W', wxMOD_CMD) + }}, + // load most recent is more commonly used than load state + // {ShortcutCommand(XRCID("Load")), + // { + // KeyboardInput('L', wxMOD_CMD) + // }}, + {ShortcutCommand(XRCID("LoadGameRecent")), + { + KeyboardInput('L', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("LoadGame01")), + { + KeyboardInput(WXK_F1, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame02")), + { + KeyboardInput(WXK_F2, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame03")), + { + KeyboardInput(WXK_F3, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame04")), + { + KeyboardInput(WXK_F4, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame05")), + { + KeyboardInput(WXK_F5, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame06")), + { + KeyboardInput(WXK_F6, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame07")), + { + KeyboardInput(WXK_F7, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame08")), + { + KeyboardInput(WXK_F8, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame09")), + { + KeyboardInput(WXK_F9, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("LoadGame10")), + { + KeyboardInput(WXK_F10, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("Pause")), + {KeyboardInput(WXK_PAUSE, wxMOD_NONE), KeyboardInput('P', wxMOD_CMD)}}, + {ShortcutCommand(XRCID("Reset")), + { + KeyboardInput('R', wxMOD_CMD) + }}, + // add shortcuts for original size multiplier #415 + {ShortcutCommand(XRCID("SetSize1x")), + { + KeyboardInput('1', wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("SetSize2x")), + { + KeyboardInput('2', wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("SetSize3x")), + { + KeyboardInput('3', wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("SetSize4x")), + { + KeyboardInput('4', wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("SetSize5x")), + { + KeyboardInput('5', wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("SetSize6x")), + { + KeyboardInput('6', wxMOD_NONE) + }}, + // save oldest is more commonly used than save other + // {ShortcutCommand(XRCID("Save")), + // { + // KeyboardInput('S', wxMOD_CMD) + // }}, + {ShortcutCommand(XRCID("SaveGameOldest")), + { + KeyboardInput('S', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("SaveGame01")), + { + KeyboardInput(WXK_F1, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame02")), + { + KeyboardInput(WXK_F2, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame03")), + { + KeyboardInput(WXK_F3, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame04")), + { + KeyboardInput(WXK_F4, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame05")), + { + KeyboardInput(WXK_F5, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame06")), + { + KeyboardInput(WXK_F6, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame07")), + { + KeyboardInput(WXK_F7, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame08")), + { + KeyboardInput(WXK_F8, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame09")), + { + KeyboardInput(WXK_F9, wxMOD_SHIFT) + }}, + {ShortcutCommand(XRCID("SaveGame10")), + { + KeyboardInput(WXK_F10, wxMOD_SHIFT) + }}, + // I prefer the SDL ESC key binding + // {ShortcutCommand(XRCID("ToggleFullscreen")), + // { + // KeyboardInput(WXK_ESCAPE, wxMOD_NONE) + // }}, + // alt-enter is more standard anyway + {ShortcutCommand(XRCID("ToggleFullscreen")), + { + KeyboardInput(WXK_RETURN, wxMOD_ALT) + }}, + {ShortcutCommand(XRCID("JoypadAutofireA")), + { + KeyboardInput('1', wxMOD_ALT) + }}, + {ShortcutCommand(XRCID("JoypadAutofireB")), + { + KeyboardInput('2', wxMOD_ALT) + }}, + {ShortcutCommand(XRCID("JoypadAutofireL")), + { + KeyboardInput('3', wxMOD_ALT) + }}, + {ShortcutCommand(XRCID("JoypadAutofireR")), + { + KeyboardInput('4', wxMOD_ALT) + }}, + {ShortcutCommand(XRCID("VideoLayersBG0")), + { + KeyboardInput('1', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersBG1")), + { + KeyboardInput('2', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersBG2")), + { + KeyboardInput('3', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersBG3")), + { + KeyboardInput('4', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersOBJ")), + { + KeyboardInput('5', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersWIN0")), + { + KeyboardInput('6', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersWIN1")), + { + KeyboardInput('7', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersOBJWIN")), + { + KeyboardInput('8', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("Rewind")), + { + KeyboardInput('B', wxMOD_CMD) + }}, + // The following commands do not have the dafault wxWidgets shortcut. + // The wxID_FILE1 shortcut is active when the first recent menu entry is populated. + // The same goes for the others, wxID_FILE2 is active when the second recent menu entry is + // populated, etc. + {ShortcutCommand(wxID_FILE1), + { + KeyboardInput(WXK_F1, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE2), + { + KeyboardInput(WXK_F2, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE4), + { + KeyboardInput(WXK_F3, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE4), + { + KeyboardInput(WXK_F4, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE5), + { + KeyboardInput(WXK_F5, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE6), + { + KeyboardInput(WXK_F6, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE7), + { + KeyboardInput(WXK_F7, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE8), + { + KeyboardInput(WXK_F8, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE9), + { + KeyboardInput(WXK_F9, wxMOD_CMD) + }}, + {ShortcutCommand(wxID_FILE10), + { + KeyboardInput(WXK_F10, wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("VideoLayersReset")), + { + KeyboardInput('0', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("ChangeFilter")), + { + KeyboardInput('G', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("ChangeIFB")), + { + KeyboardInput('I', wxMOD_CMD) + }}, + {ShortcutCommand(XRCID("IncreaseVolume")), + { + KeyboardInput(WXK_NUMPAD_ADD, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("DecreaseVolume")), + { + KeyboardInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE) + }}, + {ShortcutCommand(XRCID("ToggleSound")), + { + KeyboardInput(WXK_NUMPAD_ENTER, wxMOD_NONE) + }}, + + // Player 1 controls. + {GameCommand(GameJoy(0), config::GameKey::Up), + { + KeyboardInput('W'), + JoyInput(JoyId(0), JoyControl::Button, 11), + JoyInput(JoyId(0), JoyControl::AxisMinus, 1), + JoyInput(JoyId(0), JoyControl::AxisMinus, 3), + JoyInput(JoyId(0), JoyControl::HatNorth, 0), + }}, + {GameCommand(GameJoy(0), config::GameKey::Down), + { + KeyboardInput('S'), + JoyInput(JoyId(0), JoyControl::Button, 12), + JoyInput(JoyId(0), JoyControl::AxisPlus, 1), + JoyInput(JoyId(0), JoyControl::AxisPlus, 3), + JoyInput(JoyId(0), JoyControl::HatSouth, 0), + }}, + {GameCommand(GameJoy(0), config::GameKey::Left), + { + KeyboardInput('A'), + JoyInput(JoyId(0), JoyControl::Button, 13), + JoyInput(JoyId(0), JoyControl::AxisMinus, 0), + JoyInput(JoyId(0), JoyControl::AxisMinus, 2), + JoyInput(JoyId(0), JoyControl::HatWest, 0), + }}, + {GameCommand(GameJoy(0), config::GameKey::Right), + { + KeyboardInput('D'), + JoyInput(JoyId(0), JoyControl::Button, 14), + JoyInput(JoyId(0), JoyControl::AxisPlus, 0), + JoyInput(JoyId(0), JoyControl::AxisPlus, 2), + JoyInput(JoyId(0), JoyControl::HatEast, 0), + }}, + {GameCommand(GameJoy(0), config::GameKey::A), + { + KeyboardInput('L'), + JoyInput(JoyId(0), JoyControl::Button, 1), + }}, + {GameCommand(GameJoy(0), config::GameKey::B), + { + KeyboardInput('K'), + JoyInput(JoyId(0), JoyControl::Button, 0), + }}, + {GameCommand(GameJoy(0), config::GameKey::L), + { + KeyboardInput('I'), + JoyInput(JoyId(0), JoyControl::Button, 2), + JoyInput(JoyId(0), JoyControl::Button, 9), + JoyInput(JoyId(0), JoyControl::AxisPlus, 4), + }}, + {GameCommand(GameJoy(0), config::GameKey::R), + { + KeyboardInput('O'), + JoyInput(JoyId(0), JoyControl::Button, 3), + JoyInput(JoyId(0), JoyControl::Button, 10), + JoyInput(JoyId(0), JoyControl::AxisPlus, 5), + }}, + {GameCommand(GameJoy(0), config::GameKey::Select), + { + KeyboardInput(WXK_BACK), + JoyInput(JoyId(0), JoyControl::Button, 4), + }}, + {GameCommand(GameJoy(0), config::GameKey::Start), + { + KeyboardInput(WXK_RETURN), + JoyInput(JoyId(0), JoyControl::Button, 6), + }}, + {GameCommand(GameJoy(0), config::GameKey::MotionUp), {}}, + {GameCommand(GameJoy(0), config::GameKey::MotionDown), {}}, + {GameCommand(GameJoy(0), config::GameKey::MotionLeft), {}}, + {GameCommand(GameJoy(0), config::GameKey::MotionRight), {}}, + {GameCommand(GameJoy(0), config::GameKey::MotionIn), {}}, + {GameCommand(GameJoy(0), config::GameKey::MotionOut), {}}, + {GameCommand(GameJoy(0), config::GameKey::AutoA), {}}, + {GameCommand(GameJoy(0), config::GameKey::AutoB), {}}, + {GameCommand(GameJoy(0), config::GameKey::Speed), + { + KeyboardInput(WXK_SPACE), + }}, + {GameCommand(GameJoy(0), config::GameKey::Capture), {}}, + {GameCommand(GameJoy(0), config::GameKey::Gameshark), {}}, + + // Player 2 controls. + {GameCommand(GameJoy(1), config::GameKey::Up), + { + JoyInput(JoyId(1), JoyControl::Button, 11), + JoyInput(JoyId(1), JoyControl::AxisMinus, 1), + JoyInput(JoyId(1), JoyControl::AxisMinus, 3), + JoyInput(JoyId(1), JoyControl::HatNorth, 0), + }}, + {GameCommand(GameJoy(1), config::GameKey::Down), + { + JoyInput(JoyId(1), JoyControl::Button, 12), + JoyInput(JoyId(1), JoyControl::AxisPlus, 1), + JoyInput(JoyId(1), JoyControl::AxisPlus, 3), + JoyInput(JoyId(1), JoyControl::HatSouth, 0), + }}, + {GameCommand(GameJoy(1), config::GameKey::Left), + { + JoyInput(JoyId(1), JoyControl::Button, 13), + JoyInput(JoyId(1), JoyControl::AxisMinus, 0), + JoyInput(JoyId(1), JoyControl::AxisMinus, 2), + JoyInput(JoyId(1), JoyControl::HatWest, 0), + }}, + {GameCommand(GameJoy(1), config::GameKey::Right), + { + JoyInput(JoyId(1), JoyControl::Button, 14), + JoyInput(JoyId(1), JoyControl::AxisPlus, 0), + JoyInput(JoyId(1), JoyControl::AxisPlus, 2), + JoyInput(JoyId(1), JoyControl::HatEast, 0), + }}, + {GameCommand(GameJoy(1), config::GameKey::A), + { + JoyInput(JoyId(1), JoyControl::Button, 1), + }}, + {GameCommand(GameJoy(1), config::GameKey::B), + { + JoyInput(JoyId(1), JoyControl::Button, 0), + }}, + {GameCommand(GameJoy(1), config::GameKey::L), + { + JoyInput(JoyId(1), JoyControl::Button, 2), + JoyInput(JoyId(1), JoyControl::Button, 9), + JoyInput(JoyId(1), JoyControl::AxisPlus, 4), + }}, + {GameCommand(GameJoy(1), config::GameKey::R), + { + JoyInput(JoyId(1), JoyControl::Button, 3), + JoyInput(JoyId(1), JoyControl::Button, 10), + JoyInput(JoyId(1), JoyControl::AxisPlus, 5), + }}, + {GameCommand(GameJoy(1), config::GameKey::Select), + { + JoyInput(JoyId(1), JoyControl::Button, 4), + }}, + {GameCommand(GameJoy(1), config::GameKey::Start), + { + JoyInput(JoyId(1), JoyControl::Button, 6), + }}, + {GameCommand(GameJoy(1), config::GameKey::MotionUp), {}}, + {GameCommand(GameJoy(1), config::GameKey::MotionDown), {}}, + {GameCommand(GameJoy(1), config::GameKey::MotionLeft), {}}, + {GameCommand(GameJoy(1), config::GameKey::MotionRight), {}}, + {GameCommand(GameJoy(1), config::GameKey::MotionIn), {}}, + {GameCommand(GameJoy(1), config::GameKey::MotionOut), {}}, + {GameCommand(GameJoy(1), config::GameKey::AutoA), {}}, + {GameCommand(GameJoy(1), config::GameKey::AutoB), {}}, + {GameCommand(GameJoy(1), config::GameKey::Speed), {}}, + {GameCommand(GameJoy(1), config::GameKey::Capture), {}}, + {GameCommand(GameJoy(1), config::GameKey::Gameshark), {}}, + + // Player 3 controls. + {GameCommand(GameJoy(2), config::GameKey::Up), + { + JoyInput(JoyId(2), JoyControl::Button, 11), + JoyInput(JoyId(2), JoyControl::AxisMinus, 1), + JoyInput(JoyId(2), JoyControl::AxisMinus, 3), + JoyInput(JoyId(2), JoyControl::HatNorth, 0), + }}, + {GameCommand(GameJoy(2), config::GameKey::Down), + { + JoyInput(JoyId(2), JoyControl::Button, 12), + JoyInput(JoyId(2), JoyControl::AxisPlus, 1), + JoyInput(JoyId(2), JoyControl::AxisPlus, 3), + JoyInput(JoyId(2), JoyControl::HatSouth, 0), + }}, + {GameCommand(GameJoy(2), config::GameKey::Left), + { + JoyInput(JoyId(2), JoyControl::Button, 13), + JoyInput(JoyId(2), JoyControl::AxisMinus, 0), + JoyInput(JoyId(2), JoyControl::AxisMinus, 2), + JoyInput(JoyId(2), JoyControl::HatWest, 0), + }}, + {GameCommand(GameJoy(2), config::GameKey::Right), + { + JoyInput(JoyId(2), JoyControl::Button, 14), + JoyInput(JoyId(2), JoyControl::AxisPlus, 0), + JoyInput(JoyId(2), JoyControl::AxisPlus, 2), + JoyInput(JoyId(2), JoyControl::HatEast, 0), + }}, + {GameCommand(GameJoy(2), config::GameKey::A), + { + JoyInput(JoyId(2), JoyControl::Button, 1), + }}, + {GameCommand(GameJoy(2), config::GameKey::B), + { + JoyInput(JoyId(2), JoyControl::Button, 0), + }}, + {GameCommand(GameJoy(2), config::GameKey::L), + { + JoyInput(JoyId(2), JoyControl::Button, 2), + JoyInput(JoyId(2), JoyControl::Button, 9), + JoyInput(JoyId(2), JoyControl::AxisPlus, 4), + }}, + {GameCommand(GameJoy(2), config::GameKey::R), + { + JoyInput(JoyId(2), JoyControl::Button, 3), + JoyInput(JoyId(2), JoyControl::Button, 10), + JoyInput(JoyId(2), JoyControl::AxisPlus, 5), + }}, + {GameCommand(GameJoy(2), config::GameKey::Select), + { + JoyInput(JoyId(2), JoyControl::Button, 4), + }}, + {GameCommand(GameJoy(2), config::GameKey::Start), + { + JoyInput(JoyId(2), JoyControl::Button, 6), + }}, + {GameCommand(GameJoy(2), config::GameKey::MotionUp), {}}, + {GameCommand(GameJoy(2), config::GameKey::MotionDown), {}}, + {GameCommand(GameJoy(2), config::GameKey::MotionLeft), {}}, + {GameCommand(GameJoy(2), config::GameKey::MotionRight), {}}, + {GameCommand(GameJoy(2), config::GameKey::MotionIn), {}}, + {GameCommand(GameJoy(2), config::GameKey::MotionOut), {}}, + {GameCommand(GameJoy(2), config::GameKey::AutoA), {}}, + {GameCommand(GameJoy(2), config::GameKey::AutoB), {}}, + {GameCommand(GameJoy(2), config::GameKey::Speed), {}}, + {GameCommand(GameJoy(2), config::GameKey::Capture), {}}, + {GameCommand(GameJoy(2), config::GameKey::Gameshark), {}}, + + // Player 4 controls. + {GameCommand(GameJoy(3), config::GameKey::Up), + { + JoyInput(JoyId(3), JoyControl::Button, 11), + JoyInput(JoyId(3), JoyControl::AxisMinus, 1), + JoyInput(JoyId(3), JoyControl::AxisMinus, 3), + JoyInput(JoyId(3), JoyControl::HatNorth, 0), + }}, + {GameCommand(GameJoy(3), config::GameKey::Down), + { + JoyInput(JoyId(3), JoyControl::Button, 12), + JoyInput(JoyId(3), JoyControl::AxisPlus, 1), + JoyInput(JoyId(3), JoyControl::AxisPlus, 3), + JoyInput(JoyId(3), JoyControl::HatSouth, 0), + }}, + {GameCommand(GameJoy(3), config::GameKey::Left), + { + JoyInput(JoyId(3), JoyControl::Button, 13), + JoyInput(JoyId(3), JoyControl::AxisMinus, 0), + JoyInput(JoyId(3), JoyControl::AxisMinus, 2), + JoyInput(JoyId(3), JoyControl::HatWest, 0), + }}, + {GameCommand(GameJoy(3), config::GameKey::Right), + { + JoyInput(JoyId(3), JoyControl::Button, 14), + JoyInput(JoyId(3), JoyControl::AxisPlus, 0), + JoyInput(JoyId(3), JoyControl::AxisPlus, 2), + JoyInput(JoyId(3), JoyControl::HatEast, 0), + }}, + {GameCommand(GameJoy(3), config::GameKey::A), + { + JoyInput(JoyId(3), JoyControl::Button, 1), + }}, + {GameCommand(GameJoy(3), config::GameKey::B), + { + JoyInput(JoyId(3), JoyControl::Button, 0), + }}, + {GameCommand(GameJoy(3), config::GameKey::L), + { + JoyInput(JoyId(3), JoyControl::Button, 2), + JoyInput(JoyId(3), JoyControl::Button, 9), + JoyInput(JoyId(3), JoyControl::AxisPlus, 4), + }}, + {GameCommand(GameJoy(3), config::GameKey::R), + { + JoyInput(JoyId(3), JoyControl::Button, 3), + JoyInput(JoyId(3), JoyControl::Button, 10), + JoyInput(JoyId(3), JoyControl::AxisPlus, 5), + }}, + {GameCommand(GameJoy(3), config::GameKey::Select), + { + JoyInput(JoyId(3), JoyControl::Button, 4), + }}, + {GameCommand(GameJoy(3), config::GameKey::Start), + { + JoyInput(JoyId(3), JoyControl::Button, 6), + }}, + {GameCommand(GameJoy(3), config::GameKey::MotionUp), {}}, + {GameCommand(GameJoy(3), config::GameKey::MotionDown), {}}, + {GameCommand(GameJoy(3), config::GameKey::MotionLeft), {}}, + {GameCommand(GameJoy(3), config::GameKey::MotionRight), {}}, + {GameCommand(GameJoy(3), config::GameKey::MotionIn), {}}, + {GameCommand(GameJoy(3), config::GameKey::MotionOut), {}}, + {GameCommand(GameJoy(3), config::GameKey::AutoA), {}}, + {GameCommand(GameJoy(3), config::GameKey::AutoB), {}}, + {GameCommand(GameJoy(3), config::GameKey::Speed), {}}, + {GameCommand(GameJoy(3), config::GameKey::Capture), {}}, + {GameCommand(GameJoy(3), config::GameKey::Gameshark), {}}, + }; + // clang-format on + return kDefaultInputs; +} + +const std::unordered_set& DefaultInputsForCommand(const Command& command) { + const auto& iter = DefaultInputs().find(command); + if (iter != DefaultInputs().end()) { + return iter->second; + } + static const std::unordered_set kEmptySet; + return kEmptySet; +} + +bool IsDefaultInputForCommand(const Command& command, const UserInput& input) { + const auto& iter = DefaultInputs().find(command); + if (iter != DefaultInputs().end()) { + return iter->second.find(input) != iter->second.end(); + } + return false; +} + +} // namespace internal +} // namespace config diff --git a/src/wx/config/internal/bindings-internal.h b/src/wx/config/internal/bindings-internal.h new file mode 100644 index 00000000..a777e490 --- /dev/null +++ b/src/wx/config/internal/bindings-internal.h @@ -0,0 +1,118 @@ +#ifndef VBAM_BINDINGS_INTERNAL_INCLUDE +#error "Do not include "config/internal/bindings-internal.h" outside of the implementation." +#endif + +#include +#include +#include + +#include "wx/config/command.h" +#include "wx/config/user-input.h" + +namespace config { +namespace internal { + +// Returns the map of commands to their default inputs. +const std::unordered_map>& DefaultInputs(); + +// Returns the default inputs for the given `command`. +// Returns an empty set if there are no default inputs for `command`. +const std::unordered_set& DefaultInputsForCommand(const Command& command); + +// Returns true if `input` is the default input for `command`. +bool IsDefaultInputForCommand(const Command& command, const UserInput& input); + +// clang-format off +static constexpr std::array kOrderedGameCommands = { + GameCommand(GameJoy(0), GameKey::Up), + GameCommand(GameJoy(0), GameKey::Down), + GameCommand(GameJoy(0), GameKey::Left), + GameCommand(GameJoy(0), GameKey::Right), + GameCommand(GameJoy(0), GameKey::A), + GameCommand(GameJoy(0), GameKey::B), + GameCommand(GameJoy(0), GameKey::L), + GameCommand(GameJoy(0), GameKey::R), + GameCommand(GameJoy(0), GameKey::Select), + GameCommand(GameJoy(0), GameKey::Start), + GameCommand(GameJoy(0), GameKey::MotionUp), + GameCommand(GameJoy(0), GameKey::MotionDown), + GameCommand(GameJoy(0), GameKey::MotionLeft), + GameCommand(GameJoy(0), GameKey::MotionRight), + GameCommand(GameJoy(0), GameKey::MotionIn), + GameCommand(GameJoy(0), GameKey::MotionOut), + GameCommand(GameJoy(0), GameKey::AutoA), + GameCommand(GameJoy(0), GameKey::AutoB), + GameCommand(GameJoy(0), GameKey::Speed), + GameCommand(GameJoy(0), GameKey::Capture), + GameCommand(GameJoy(0), GameKey::Gameshark), + + GameCommand(GameJoy(1), GameKey::Up), + GameCommand(GameJoy(1), GameKey::Down), + GameCommand(GameJoy(1), GameKey::Left), + GameCommand(GameJoy(1), GameKey::Right), + GameCommand(GameJoy(1), GameKey::A), + GameCommand(GameJoy(1), GameKey::B), + GameCommand(GameJoy(1), GameKey::L), + GameCommand(GameJoy(1), GameKey::R), + GameCommand(GameJoy(1), GameKey::Select), + GameCommand(GameJoy(1), GameKey::Start), + GameCommand(GameJoy(1), GameKey::MotionUp), + GameCommand(GameJoy(1), GameKey::MotionDown), + GameCommand(GameJoy(1), GameKey::MotionLeft), + GameCommand(GameJoy(1), GameKey::MotionRight), + GameCommand(GameJoy(1), GameKey::MotionIn), + GameCommand(GameJoy(1), GameKey::MotionOut), + GameCommand(GameJoy(1), GameKey::AutoA), + GameCommand(GameJoy(1), GameKey::AutoB), + GameCommand(GameJoy(1), GameKey::Speed), + GameCommand(GameJoy(1), GameKey::Capture), + GameCommand(GameJoy(1), GameKey::Gameshark), + + GameCommand(GameJoy(2), GameKey::Up), + GameCommand(GameJoy(2), GameKey::Down), + GameCommand(GameJoy(2), GameKey::Left), + GameCommand(GameJoy(2), GameKey::Right), + GameCommand(GameJoy(2), GameKey::A), + GameCommand(GameJoy(2), GameKey::B), + GameCommand(GameJoy(2), GameKey::L), + GameCommand(GameJoy(2), GameKey::R), + GameCommand(GameJoy(2), GameKey::Select), + GameCommand(GameJoy(2), GameKey::Start), + GameCommand(GameJoy(2), GameKey::MotionUp), + GameCommand(GameJoy(2), GameKey::MotionDown), + GameCommand(GameJoy(2), GameKey::MotionLeft), + GameCommand(GameJoy(2), GameKey::MotionRight), + GameCommand(GameJoy(2), GameKey::MotionIn), + GameCommand(GameJoy(2), GameKey::MotionOut), + GameCommand(GameJoy(2), GameKey::AutoA), + GameCommand(GameJoy(2), GameKey::AutoB), + GameCommand(GameJoy(2), GameKey::Speed), + GameCommand(GameJoy(2), GameKey::Capture), + GameCommand(GameJoy(2), GameKey::Gameshark), + + GameCommand(GameJoy(3), GameKey::Up), + GameCommand(GameJoy(3), GameKey::Down), + GameCommand(GameJoy(3), GameKey::Left), + GameCommand(GameJoy(3), GameKey::Right), + GameCommand(GameJoy(3), GameKey::A), + GameCommand(GameJoy(3), GameKey::B), + GameCommand(GameJoy(3), GameKey::L), + GameCommand(GameJoy(3), GameKey::R), + GameCommand(GameJoy(3), GameKey::Select), + GameCommand(GameJoy(3), GameKey::Start), + GameCommand(GameJoy(3), GameKey::MotionUp), + GameCommand(GameJoy(3), GameKey::MotionDown), + GameCommand(GameJoy(3), GameKey::MotionLeft), + GameCommand(GameJoy(3), GameKey::MotionRight), + GameCommand(GameJoy(3), GameKey::MotionIn), + GameCommand(GameJoy(3), GameKey::MotionOut), + GameCommand(GameJoy(3), GameKey::AutoA), + GameCommand(GameJoy(3), GameKey::AutoB), + GameCommand(GameJoy(3), GameKey::Speed), + GameCommand(GameJoy(3), GameKey::Capture), + GameCommand(GameJoy(3), GameKey::Gameshark), +}; +// clang-format on + +} // namespace internal +} // namespace config diff --git a/src/wx/config/internal/shortcuts-internal.cpp b/src/wx/config/internal/shortcuts-internal.cpp deleted file mode 100644 index 83e8ba7a..00000000 --- a/src/wx/config/internal/shortcuts-internal.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "wx/config/shortcuts.h" -#include "wx/config/user-input.h" - -#include - -#define VBAM_SHORTCUTS_INTERNAL_INCLUDE -#include "wx/config/internal/shortcuts-internal.h" -#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE - -namespace config { -namespace internal { - -const std::unordered_map& DefaultShortcuts() { - static const std::unordered_map kDefaultShortcuts = { - {XRCID("CheatsList"), KeyboardInput('C', wxMOD_CMD)}, - {XRCID("NextFrame"), KeyboardInput('N', wxMOD_CMD)}, - // this was annoying people A LOT #334 - //{wxID_EXIT, KeyboardInput(WXK_ESCAPE, wxMOD_NONE)}, - // this was annoying people #298 - //{wxID_EXIT, KeyboardInput('X', wxMOD_CMD)}, - - {wxID_EXIT, KeyboardInput('Q', wxMOD_CMD)}, - {wxID_CLOSE, KeyboardInput('W', wxMOD_CMD)}, - // load most recent is more commonly used than load state - // {XRCID("Load"), KeyboardInput('L', wxMOD_CMD)}, - {XRCID("LoadGameRecent"), KeyboardInput('L', wxMOD_CMD)}, - {XRCID("LoadGame01"), KeyboardInput(WXK_F1, wxMOD_NONE)}, - {XRCID("LoadGame02"), KeyboardInput(WXK_F2, wxMOD_NONE)}, - {XRCID("LoadGame03"), KeyboardInput(WXK_F3, wxMOD_NONE)}, - {XRCID("LoadGame04"), KeyboardInput(WXK_F4, wxMOD_NONE)}, - {XRCID("LoadGame05"), KeyboardInput(WXK_F5, wxMOD_NONE)}, - {XRCID("LoadGame06"), KeyboardInput(WXK_F6, wxMOD_NONE)}, - {XRCID("LoadGame07"), KeyboardInput(WXK_F7, wxMOD_NONE)}, - {XRCID("LoadGame08"), KeyboardInput(WXK_F8, wxMOD_NONE)}, - {XRCID("LoadGame09"), KeyboardInput(WXK_F9, wxMOD_NONE)}, - {XRCID("LoadGame10"), KeyboardInput(WXK_F10, wxMOD_NONE)}, - {XRCID("Pause"), KeyboardInput(WXK_PAUSE, wxMOD_NONE)}, - {XRCID("Pause"), KeyboardInput('P', wxMOD_CMD)}, - {XRCID("Reset"), KeyboardInput('R', wxMOD_CMD)}, - // add shortcuts for original size multiplier #415 - {XRCID("SetSize1x"), KeyboardInput('1', wxMOD_NONE)}, - {XRCID("SetSize2x"), KeyboardInput('2', wxMOD_NONE)}, - {XRCID("SetSize3x"), KeyboardInput('3', wxMOD_NONE)}, - {XRCID("SetSize4x"), KeyboardInput('4', wxMOD_NONE)}, - {XRCID("SetSize5x"), KeyboardInput('5', wxMOD_NONE)}, - {XRCID("SetSize6x"), KeyboardInput('6', wxMOD_NONE)}, - // save oldest is more commonly used than save other - // {XRCID("Save"), KeyboardInput('S', wxMOD_CMD)}, - {XRCID("SaveGameOldest"), KeyboardInput('S', wxMOD_CMD)}, - {XRCID("SaveGame01"), KeyboardInput(WXK_F1, wxMOD_SHIFT)}, - {XRCID("SaveGame02"), KeyboardInput(WXK_F2, wxMOD_SHIFT)}, - {XRCID("SaveGame03"), KeyboardInput(WXK_F3, wxMOD_SHIFT)}, - {XRCID("SaveGame04"), KeyboardInput(WXK_F4, wxMOD_SHIFT)}, - {XRCID("SaveGame05"), KeyboardInput(WXK_F5, wxMOD_SHIFT)}, - {XRCID("SaveGame06"), KeyboardInput(WXK_F6, wxMOD_SHIFT)}, - {XRCID("SaveGame07"), KeyboardInput(WXK_F7, wxMOD_SHIFT)}, - {XRCID("SaveGame08"), KeyboardInput(WXK_F8, wxMOD_SHIFT)}, - {XRCID("SaveGame09"), KeyboardInput(WXK_F9, wxMOD_SHIFT)}, - {XRCID("SaveGame10"), KeyboardInput(WXK_F10, wxMOD_SHIFT)}, - // I prefer the SDL ESC key binding - // {XRCID("ToggleFullscreen"), KeyboardInput(WXK_ESCAPE, wxMOD_NONE)}, - // alt-enter is more standard anyway - {XRCID("ToggleFullscreen"), KeyboardInput(WXK_RETURN, wxMOD_ALT)}, - {XRCID("JoypadAutofireA"), KeyboardInput('1', wxMOD_ALT)}, - {XRCID("JoypadAutofireB"), KeyboardInput('2', wxMOD_ALT)}, - {XRCID("JoypadAutofireL"), KeyboardInput('3', wxMOD_ALT)}, - {XRCID("JoypadAutofireR"), KeyboardInput('4', wxMOD_ALT)}, - {XRCID("VideoLayersBG0"), KeyboardInput('1', wxMOD_CMD)}, - {XRCID("VideoLayersBG1"), KeyboardInput('2', wxMOD_CMD)}, - {XRCID("VideoLayersBG2"), KeyboardInput('3', wxMOD_CMD)}, - {XRCID("VideoLayersBG3"), KeyboardInput('4', wxMOD_CMD)}, - {XRCID("VideoLayersOBJ"), KeyboardInput('5', wxMOD_CMD)}, - {XRCID("VideoLayersWIN0"), KeyboardInput('6', wxMOD_CMD)}, - {XRCID("VideoLayersWIN1"), KeyboardInput('7', wxMOD_CMD)}, - {XRCID("VideoLayersOBJWIN"), KeyboardInput('8', wxMOD_CMD)}, - {XRCID("Rewind"), KeyboardInput('B', wxMOD_CMD)}, - // following are not in standard menus - // FILExx are filled in when recent menu is filled - {wxID_FILE1, KeyboardInput(WXK_F1, wxMOD_CMD)}, - {wxID_FILE2, KeyboardInput(WXK_F2, wxMOD_CMD)}, - {wxID_FILE3, KeyboardInput(WXK_F3, wxMOD_CMD)}, - {wxID_FILE4, KeyboardInput(WXK_F4, wxMOD_CMD)}, - {wxID_FILE5, KeyboardInput(WXK_F5, wxMOD_CMD)}, - {wxID_FILE6, KeyboardInput(WXK_F6, wxMOD_CMD)}, - {wxID_FILE7, KeyboardInput(WXK_F7, wxMOD_CMD)}, - {wxID_FILE8, KeyboardInput(WXK_F8, wxMOD_CMD)}, - {wxID_FILE9, KeyboardInput(WXK_F9, wxMOD_CMD)}, - {wxID_FILE10, KeyboardInput(WXK_F10, wxMOD_CMD)}, - {XRCID("VideoLayersReset"), KeyboardInput('0', wxMOD_CMD)}, - {XRCID("ChangeFilter"), KeyboardInput('G', wxMOD_CMD)}, - {XRCID("ChangeIFB"), KeyboardInput('I', wxMOD_CMD)}, - {XRCID("IncreaseVolume"), KeyboardInput(WXK_NUMPAD_ADD, wxMOD_NONE)}, - {XRCID("DecreaseVolume"), KeyboardInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE)}, - {XRCID("ToggleSound"), KeyboardInput(WXK_NUMPAD_ENTER, wxMOD_NONE)}, - }; - return kDefaultShortcuts; -} - -UserInput DefaultShortcutForCommand(int command) { - const auto& iter = DefaultShortcuts().find(command); - if (iter != DefaultShortcuts().end()) { - return iter->second; - } - return UserInput(); -} - -} // namespace internal -} // namespace config diff --git a/src/wx/config/internal/shortcuts-internal.h b/src/wx/config/internal/shortcuts-internal.h deleted file mode 100644 index 01b44820..00000000 --- a/src/wx/config/internal/shortcuts-internal.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef VBAM_SHORTCUTS_INTERNAL_INCLUDE -#error "Do not include "config/internal/shortcuts-internal.h" outside of the implementation." -#endif - -#include -#include - -#include "wx/config/user-input.h" - -namespace config { -namespace internal { - -// Returns the map of commands to their default shortcut. -const std::unordered_map& DefaultShortcuts(); - -// Returns the default shortcut for the given `command`. -// Returns an Invalid UserInput if there is no default shortcut for `command`. -UserInput DefaultShortcutForCommand(int command); - -} // namespace internal -} // namespace config diff --git a/src/wx/config/shortcuts.cpp b/src/wx/config/shortcuts.cpp deleted file mode 100644 index a73bb843..00000000 --- a/src/wx/config/shortcuts.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "wx/config/shortcuts.h" - -#include -#include -#include -#include - -#include "wx/config/user-input.h" - -#define VBAM_SHORTCUTS_INTERNAL_INCLUDE -#include "wx/config/internal/shortcuts-internal.h" -#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE - -namespace config { - -namespace { - -int NoopCommand() { - static const int noop = XRCID("NOOP"); - return noop; -} - -} // namespace - -Shortcuts::Shortcuts() { - // Set up default shortcuts. - for (const auto& iter : internal::DefaultShortcuts()) { - AssignInputToCommand(iter.second, iter.first); - } -} - -Shortcuts::Shortcuts(const std::unordered_map>& command_to_inputs, - const std::unordered_map& input_to_command, - const std::unordered_map& disabled_defaults) - : command_to_inputs_(command_to_inputs.begin(), command_to_inputs.end()), - input_to_command_(input_to_command.begin(), input_to_command.end()), - disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {} - -std::vector> Shortcuts::GetKeyboardConfiguration() const { - std::vector> config; - config.reserve(command_to_inputs_.size() + 1); - - if (!disabled_defaults_.empty()) { - std::unordered_set noop_inputs; - for (const auto& iter : disabled_defaults_) { - noop_inputs.insert(iter.first); - } - config.push_back(std::make_pair(NoopCommand(), UserInput::SpanToConfigString(noop_inputs))); - } - - for (const auto& iter : command_to_inputs_) { - std::unordered_set inputs; - for (const auto& input : iter.second) { - if (internal::DefaultShortcutForCommand(iter.first) != input) { - // Not a default input. - inputs.insert(input); - } - } - if (!inputs.empty()) { - config.push_back(std::make_pair(iter.first, UserInput::SpanToConfigString(inputs))); - } - } - - return config; -} - -std::unordered_set Shortcuts::InputsForCommand(int command) const { - if (command == NoopCommand()) { - std::unordered_set noop_inputs; - for (const auto& iter : disabled_defaults_) { - noop_inputs.insert(iter.first); - } - return noop_inputs; - } - - auto iter = command_to_inputs_.find(command); - if (iter == command_to_inputs_.end()) { - return {}; - } - return iter->second; -} - -int Shortcuts::CommandForInput(const UserInput& input) const { - const auto iter = input_to_command_.find(input); - if (iter == input_to_command_.end()) { - return 0; - } - return iter->second; -} - -Shortcuts Shortcuts::Clone() const { - return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_); -} - -void Shortcuts::AssignInputToCommand(const UserInput& input, int command) { - if (command == NoopCommand()) { - // "Assigning to Noop" means unassinging the default binding. - UnassignDefaultBinding(input); - return; - } - - // Remove the existing binding if it exists. - auto iter = input_to_command_.find(input); - if (iter != input_to_command_.end()) { - UnassignInput(input); - } - - auto disabled_iter = disabled_defaults_.find(input); - if (disabled_iter != disabled_defaults_.end()) { - int original_command = disabled_iter->second; - if (original_command == command) { - // Restoring a disabled input. Remove from the disabled set. - disabled_defaults_.erase(disabled_iter); - } - // Then, just continue normally. - } - - command_to_inputs_[command].emplace(input); - input_to_command_[input] = command; -} - -void Shortcuts::UnassignInput(const UserInput& input) { - assert(input); - - auto iter = input_to_command_.find(input); - if (iter == input_to_command_.end()) { - // Input not found, nothing to do. - return; - } - - if (internal::DefaultShortcutForCommand(iter->second) == input) { - // Unassigning a default binding has some special handling. - UnassignDefaultBinding(input); - return; - } - - // Otherwise, just remove it from the 2 maps. - auto command_iter = command_to_inputs_.find(iter->second); - assert(command_iter != command_to_inputs_.end()); - - command_iter->second.erase(input); - if (command_iter->second.empty()) { - // Remove empty set. - command_to_inputs_.erase(command_iter); - } - input_to_command_.erase(iter); -} - -void Shortcuts::UnassignDefaultBinding(const UserInput& input) { - auto input_iter = input_to_command_.find(input); - if (input_iter == input_to_command_.end()) { - // This can happen if the INI file provided by the user has an invalid - // option. In this case, just silently ignore it. - return; - } - - if (internal::DefaultShortcutForCommand(input_iter->second) != input) { - // As above, we have already removed the default binding, ignore it. - return; - } - - auto command_iter = command_to_inputs_.find(input_iter->second); - assert(command_iter != command_to_inputs_.end()); - - command_iter->second.erase(input); - if (command_iter->second.empty()) { - command_to_inputs_.erase(command_iter); - } - - disabled_defaults_[input] = input_iter->second; - input_to_command_.erase(input_iter); -} - -} // namespace config diff --git a/src/wx/config/shortcuts.h b/src/wx/config/shortcuts.h deleted file mode 100644 index 994045b7..00000000 --- a/src/wx/config/shortcuts.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef VBAM_WX_CONFIG_SHORTCUTS_H_ -#define VBAM_WX_CONFIG_SHORTCUTS_H_ - -#include -#include -#include -#include - -#include "wx/config/user-input.h" - -// wxWidgets only goes up to `wxID_FILE9` but we want 10 recent files. -#define wxID_FILE10 (wxID_FILE9 + 1) - -namespace config { - -// Represents a set of shortcuts from a user input (keyboard or joypad) to a -// command. Internally, this class keeps track of all the necessary data for -// resolving a user input to a command at runtime and for updating the INI file. -class Shortcuts { -public: - Shortcuts(); - ~Shortcuts() = default; - - Shortcuts(Shortcuts&&) noexcept = default; - Shortcuts& operator=(Shortcuts&&) noexcept = default; - - // Disable copy and copy assignment operator. - // Clone() is provided only for the configuration window, this class - // should otherwise be treated as move-only. If you wish to access the - // Shortcuts configuration, do it from wxGetApp()->GameControlState(). - Shortcuts(const Shortcuts&) = delete; - Shortcuts& operator=(const Shortcuts&) = delete; - - // Returns the shortcuts configuration for the INI file. - // Internally, there are global default system inputs that are immediately - // available on first run. For the configuration saved in the [Keyboard] - // section of the vbam.ini file, we only keep track of the following: - // - Disabled default input. These appear under [Keyboard/NOOP]. - // - User-added custom bindings. These appear under [Keyboard/CommandName]. - // Essentially, this is a diff between the default shortcuts and the user - // configuration. - std::vector> GetKeyboardConfiguration() const; - - // Returns the list of input currently configured for `command`. - std::unordered_set InputsForCommand(int command) const; - - // Returns the command currently assigned to `input` or nullptr if none. - int CommandForInput(const UserInput& input) const; - - // Returns a copy of this object. This can be an expensive operation and - // should only be used to modify the currently active shortcuts - // configuration. - Shortcuts Clone() const; - - // Assigns `input` to `command`. Silently unassigns `input` if it is already - // assigned to another command. - void AssignInputToCommand(const UserInput& input, const int command); - - // Removes `input` assignment. No-op if `input` is not assigned. `input` - // must be a valid UserInput. Call will assert otherwise. - void UnassignInput(const UserInput& input); - -private: - // Faster constructor for explicit copy. - Shortcuts(const std::unordered_map>& command_to_inputs, - const std::unordered_map& input_to_command, - const std::unordered_map& disabled_defaults); - - // Helper method to unassign a binding used by the default configuration. - // This requires special handling since the INI configuration is a diff - // between the default bindings and the user configuration. - void UnassignDefaultBinding(const UserInput& input); - - // Map of command to their associated input set. - std::unordered_map> command_to_inputs_; - // Reverse map of the above. An input can only map to a single command. - std::unordered_map input_to_command_; - // Disabled default shortcuts. This is used to easily retrieve the - // configuration to save in the INI file. - std::unordered_map disabled_defaults_; -}; - -using ShortcutsProvider = std::function; - -} // namespace config - -#endif // VBAM_WX_CONFIG_SHORTCUTS_H_ diff --git a/src/wx/config/user-input.h b/src/wx/config/user-input.h index 41fa2c79..5fb50aa9 100644 --- a/src/wx/config/user-input.h +++ b/src/wx/config/user-input.h @@ -207,14 +207,14 @@ private: // Specializations for hash functions for all of the above classes. template <> struct std::hash { - std::size_t operator()(config::JoyId const& joy_id) const noexcept { + std::size_t operator()(const config::JoyId& joy_id) const noexcept { return std::hash{}(joy_id.sdl_index_); } }; template <> struct std::hash { - std::size_t operator()(config::JoyInput const& joy_input) const noexcept { + std::size_t operator()(const config::JoyInput& joy_input) const noexcept { const std::size_t hash1 = std::hash{}(joy_input.joy()); const std::size_t hash2 = std::hash{}(static_cast(joy_input.control())); const std::size_t hash3 = std::hash{}(joy_input.control_index()); @@ -224,7 +224,7 @@ struct std::hash { template <> struct std::hash { - std::size_t operator()(config::KeyboardInput const& keyboard_input) const noexcept { + std::size_t operator()(const config::KeyboardInput& keyboard_input) const noexcept { const std::size_t hash1 = std::hash{}(keyboard_input.key()); const std::size_t hash2 = std::hash{}(keyboard_input.mod()); return hash1 ^ hash2; @@ -233,7 +233,7 @@ struct std::hash { template <> struct std::hash { - std::size_t operator()(config::UserInput const& user_input) const noexcept { + std::size_t operator()(const config::UserInput& user_input) const noexcept { const std::size_t device_hash = std::hash{}(static_cast(user_input.device())); switch (user_input.device()) { case config::UserInput::Device::Invalid: diff --git a/src/wx/dialogs/accel-config.cpp b/src/wx/dialogs/accel-config.cpp index eedffccc..a28e7769 100644 --- a/src/wx/dialogs/accel-config.cpp +++ b/src/wx/dialogs/accel-config.cpp @@ -6,9 +6,11 @@ #include #include -#include "wx/config/shortcuts.h" +#include "wx/config/bindings.h" +#include "wx/config/command.h" #include "wx/config/user-input.h" #include "wx/dialogs/base-dialog.h" +#include "wx/widgets/client-data.h" #include "wx/widgets/user-input-ctrl.h" #include "wx/wxvbam.h" @@ -16,38 +18,28 @@ namespace dialogs { namespace { -// Client data holding a config::UserInput. -class UserInputClientData : public wxClientData { -public: - explicit UserInputClientData(const config::UserInput& user_input) : user_input_(user_input) {} - explicit UserInputClientData(config::UserInput&& user_input) - : user_input_(std::move(user_input)) {} - ~UserInputClientData() override = default; - - const config::UserInput& user_input() const { return user_input_; } - -private: - const config::UserInput user_input_; -}; +using UserInputClientData = widgets::ClientData; // Holds a Command reference and the corresponding string used for current // assignment reference for fast access at configuration time. Owned by // the corresponding wxTreeItem. class CommandTreeItemData : public wxTreeItemData { public: - CommandTreeItemData(int command, wxString assigned_string, wxString message_string) + CommandTreeItemData(config::ShortcutCommand command, + wxString assigned_string, + wxString message_string) : wxTreeItemData(), command_(command), assigned_string_(std::move(assigned_string)), message_string_(std::move(message_string)) {} ~CommandTreeItemData() override = default; - int command() const { return command_; } + config::ShortcutCommand command() const { return command_; } const wxString& assigned_string() const { return assigned_string_; }; const wxString& message_string() const { return message_string_; }; private: - const int command_; + const config::ShortcutCommand command_; const wxString assigned_string_; const wxString message_string_; }; @@ -61,7 +53,7 @@ wxString AppendMenuItem(const wxString& prefix, int level, const wxMenuItem* men menu_item->GetItemLabelText() + (menu_item->IsSubMenu() ? "\n" : "")); } -void AppendItemToTree(std::unordered_map* command_to_item_id, +void AppendItemToTree(std::unordered_map* command_to_item_id, wxTreeCtrl* tree, const wxTreeItemId& parent, int command, @@ -75,25 +67,26 @@ void AppendItemToTree(std::unordered_map* command_to_item_id, } assert(i < ncmds); - const wxTreeItemId tree_item_id = - tree->AppendItem(parent, - /*text=*/cmdtab[i].name, - /*image=*/-1, - /*selImage=*/-1, - /*data=*/ - new CommandTreeItemData( - command, AppendString(prefix, level, cmdtab[i].name), cmdtab[i].name)); + const wxTreeItemId tree_item_id = tree->AppendItem( + parent, + /*text=*/cmdtab[i].name, + /*image=*/-1, + /*selImage=*/-1, + /*data=*/ + new CommandTreeItemData(config::ShortcutCommand(command), + AppendString(prefix, level, cmdtab[i].name), cmdtab[i].name)); command_to_item_id->emplace(command, tree_item_id); } // Built the initial tree control from the menu. -void PopulateTreeWithMenu(std::unordered_map* command_to_item_id, - wxTreeCtrl* tree, - const wxTreeItemId& parent, - wxMenu* menu, - const wxMenu* recents, - const wxString& prefix, - int level = 1) { +void PopulateTreeWithMenu( + std::unordered_map* command_to_item_id, + wxTreeCtrl* tree, + const wxTreeItemId& parent, + wxMenu* menu, + const wxMenu* recents, + const wxString& prefix, + int level = 1) { for (auto menu_item : menu->GetMenuItems()) { if (menu_item->IsSeparator()) { tree->AppendItem(parent, "-----"); @@ -125,18 +118,18 @@ void PopulateTreeWithMenu(std::unordered_map* command_to_item AccelConfig* AccelConfig::NewInstance(wxWindow* parent, wxMenuBar* menu, wxMenu* recents, - const config::ShortcutsProvider shortcuts_provider) { + const config::BindingsProvider bindings_provider) { assert(parent); assert(menu); assert(recents); - return new AccelConfig(parent, menu, recents, shortcuts_provider); + return new AccelConfig(parent, menu, recents, bindings_provider); } AccelConfig::AccelConfig(wxWindow* parent, wxMenuBar* menu, wxMenu* recents, - const config::ShortcutsProvider shortcuts_provider) - : BaseDialog(parent, "AccelConfig"), shortcuts_provider_(shortcuts_provider) { + const config::BindingsProvider bindings_provider) + : BaseDialog(parent, "AccelConfig"), bindings_provider_(bindings_provider) { assert(menu); // Loads the various dialog elements. @@ -219,11 +212,11 @@ void AccelConfig::OnDialogShown(wxShowEvent& ev) { remove_button_->Enable(false); currently_assigned_label_->SetLabel(""); - config_shortcuts_ = shortcuts_provider_()->Clone(); + config_shortcuts_ = bindings_provider_()->Clone(); } void AccelConfig::OnValidate(wxCommandEvent& ev) { - *shortcuts_provider_() = std::move(config_shortcuts_); + *bindings_provider_() = std::move(config_shortcuts_); ev.Skip(); } @@ -243,7 +236,7 @@ void AccelConfig::OnCommandSelected(wxTreeEvent& ev) { return; } - selected_command_ = command_tree_data->command(); + selected_command_ = command_tree_data->command().id(); PopulateCurrentKeys(); } @@ -257,8 +250,7 @@ void AccelConfig::OnRemoveBinding(wxCommandEvent&) { return; } - config_shortcuts_.UnassignInput( - static_cast(current_keys_->GetClientObject(selection))->user_input()); + config_shortcuts_.UnassignInput(UserInputClientData::From(current_keys_)); PopulateCurrentKeys(); } @@ -269,7 +261,7 @@ void AccelConfig::OnResetAll(wxCommandEvent&) { return; } - config_shortcuts_ = config::Shortcuts(); + config_shortcuts_ = config::Bindings(); tree_->Unselect(); key_input_->Clear(); PopulateCurrentKeys(); @@ -288,19 +280,29 @@ void AccelConfig::OnAssignBinding(wxCommandEvent&) { return; } - const int old_command = config_shortcuts_.CommandForInput(user_input); - if (old_command != 0) { - const auto iter = command_to_item_id_.find(old_command); - assert(iter != command_to_item_id_.end()); - const CommandTreeItemData* old_command_item_data = - static_cast(tree_->GetItemData(iter->second)); - assert(old_command_item_data); + const nonstd::optional old_command = + config_shortcuts_.CommandForInput(user_input); + if (old_command != nonstd::nullopt) { + wxString old_command_name; // Require user confirmation to override. + switch (old_command->tag()) { + case config::Command::Tag::kGame: + old_command_name = old_command->game().ToUXString(); + break; + case config::Command::Tag::kShortcut: + const auto iter = command_to_item_id_.find(old_command->shortcut()); + assert(iter != command_to_item_id_.end()); + const CommandTreeItemData* old_command_item_data = + static_cast(tree_->GetItemData(iter->second)); + assert(old_command_item_data); + old_command_name = old_command_item_data->message_string(); + break; + } + const int confirmation = wxMessageBox(wxString::Format(_("This will unassign \"%s\" from \"%s\". Are you sure?"), - user_input.ToLocalizedString(), - old_command_item_data->message_string()), + user_input.ToLocalizedString(), old_command_name), _("Confirm"), wxYES_NO); if (confirmation != wxYES) { return; @@ -319,16 +321,24 @@ void AccelConfig::OnKeyInput(wxCommandEvent&) { return; } - const int command = config_shortcuts_.CommandForInput(user_input); - if (command == 0) { + const auto command = config_shortcuts_.CommandForInput(user_input); + if (!command) { // No existing assignment. currently_assigned_label_->SetLabel(wxEmptyString); } else { // Existing assignment, inform the user. - const auto iter = command_to_item_id_.find(command); - assert(iter != command_to_item_id_.end()); - currently_assigned_label_->SetLabel( - static_cast(tree_->GetItemData(iter->second))->assigned_string()); + switch (command->tag()) { + case config::Command::Tag::kGame: + currently_assigned_label_->SetLabel(command->game().ToUXString()); + break; + case config::Command::Tag::kShortcut: + const auto iter = command_to_item_id_.find(command->shortcut()); + assert(iter != command_to_item_id_.end()); + currently_assigned_label_->SetLabel( + static_cast(tree_->GetItemData(iter->second)) + ->assigned_string()); + break; + } } assign_button_->Enable(true); @@ -342,9 +352,11 @@ void AccelConfig::PopulateCurrentKeys() { return; } + const config::ShortcutCommand command(selected_command_); + // Populate `current_keys`. int new_keys_count = 0; - for (const auto& user_input : config_shortcuts_.InputsForCommand(selected_command_)) { + for (const auto& user_input : config_shortcuts_.InputsForCommand(command)) { current_keys_->Append(user_input.ToLocalizedString(), new UserInputClientData(user_input)); new_keys_count++; } @@ -357,7 +369,6 @@ void AccelConfig::PopulateCurrentKeys() { current_keys_->SetSelection(std::min(previous_selection, new_keys_count - 1)); remove_button_->Enable(true); } - } } // namespace dialogs diff --git a/src/wx/dialogs/accel-config.h b/src/wx/dialogs/accel-config.h index cf16658e..1614155f 100644 --- a/src/wx/dialogs/accel-config.h +++ b/src/wx/dialogs/accel-config.h @@ -5,7 +5,8 @@ #include -#include "wx/config/shortcuts.h" +#include "wx/config/bindings.h" +#include "wx/config/command.h" #include "wx/dialogs/base-dialog.h" // Forward declarations. @@ -27,7 +28,7 @@ public: static AccelConfig* NewInstance(wxWindow* parent, wxMenuBar* menu_bar, wxMenu* recents, - const config::ShortcutsProvider shortcuts_provider); + const config::BindingsProvider bindings_provider); ~AccelConfig() override = default; @@ -38,7 +39,7 @@ private: AccelConfig(wxWindow* parent, wxMenuBar* menu_bar, wxMenu* recents, - const config::ShortcutsProvider shortcuts_provider); + const config::BindingsProvider bindings_provider); // Re-initializes the configuration. void OnDialogShown(wxShowEvent& ev); @@ -75,12 +76,12 @@ private: wxWindow* remove_button_; widgets::UserInputCtrl* key_input_; wxControl* currently_assigned_label_; - std::unordered_map command_to_item_id_; + std::unordered_map command_to_item_id_; - config::Shortcuts config_shortcuts_; + config::Bindings config_shortcuts_; int selected_command_ = 0; - const config::ShortcutsProvider shortcuts_provider_; + const config::BindingsProvider bindings_provider_; }; } // namespace dialogs diff --git a/src/wx/dialogs/joypad-config.cpp b/src/wx/dialogs/joypad-config.cpp index b10cce78..bd973aae 100644 --- a/src/wx/dialogs/joypad-config.cpp +++ b/src/wx/dialogs/joypad-config.cpp @@ -2,34 +2,96 @@ #include +#include "wx/config/command.h" #include "wx/config/option-proxy.h" #include "wx/dialogs/base-dialog.h" -#include "wx/opts.h" +#include "wx/widgets/client-data.h" #include "wx/widgets/option-validator.h" #include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/utils.h" namespace dialogs { -// static -JoypadConfig* JoypadConfig::NewInstance(wxWindow* parent) { - assert(parent); - return new JoypadConfig(parent); +namespace { + +using GameJoyClientData = widgets::ClientData; + +// A validator for the UserInputCtrl. This validator is used to transfer the +// GameControl data to and from the UserInputCtrl. `bindings_provider` must +// outlive this object. +class UserInputCtrlValidator : public wxValidator { +public: + explicit UserInputCtrlValidator(const config::GameCommand game_control, + const config::BindingsProvider bindings_provider); + ~UserInputCtrlValidator() override = default; + + wxObject* Clone() const override; + +protected: + // wxValidator implementation. + bool TransferToWindow() override; + bool TransferFromWindow() override; + bool Validate(wxWindow*) override { return true; } + + const config::GameCommand game_control_; + const config::BindingsProvider bindings_provider; +}; + +UserInputCtrlValidator::UserInputCtrlValidator(const config::GameCommand game_control, + config::BindingsProvider const bindings_provider) + : wxValidator(), game_control_(game_control), bindings_provider(bindings_provider) { + assert(bindings_provider); } -JoypadConfig::JoypadConfig(wxWindow* parent) : BaseDialog(parent, "JoypadConfig") { +wxObject* UserInputCtrlValidator::Clone() const { + return new UserInputCtrlValidator(game_control_, bindings_provider); +} + +bool UserInputCtrlValidator::TransferToWindow() { + widgets::UserInputCtrl* control = wxDynamicCast(GetWindow(), widgets::UserInputCtrl); + assert(control); + + control->SetInputs(bindings_provider()->InputsForCommand(config::Command(game_control_))); + return true; +} + +bool UserInputCtrlValidator::TransferFromWindow() { + widgets::UserInputCtrl* control = wxDynamicCast(GetWindow(), widgets::UserInputCtrl); + assert(control); + + bindings_provider()->ClearCommandAssignments(config::Command(game_control_)); + for (const auto& input : control->inputs()) { + bindings_provider()->AssignInputToCommand(input, config::Command(game_control_)); + } + + return true; +} + +} // namespace + +// static +JoypadConfig* JoypadConfig::NewInstance(wxWindow* parent, + const config::BindingsProvider bindings_provider) { + assert(parent); + assert(bindings_provider); + return new JoypadConfig(parent, bindings_provider); +} + +JoypadConfig::JoypadConfig(wxWindow* parent, const config::BindingsProvider bindings_provider) + : BaseDialog(parent, "JoypadConfig"), bindings_provider_(bindings_provider) { this->Bind(wxEVT_CHECKBOX, std::bind(&JoypadConfig::ToggleSDLGameControllerMode, this), XRCID("SDLGameControllerMode")); GetValidatedChild("SDLGameControllerMode") ->SetValue(OPTION(kSDLGameControllerMode)); - for (int joypad = 0; joypad < 4; joypad++) { - wxWindow* panel = GetValidatedChild(wxString::Format("joy%d", joypad + 1)); + for (const config::GameJoy& joypad : config::kAllGameJoys) { + wxWindow* panel = GetValidatedChild(wxString::Format("joy%zu", joypad.ux_index())); + panel->SetClientObject(new GameJoyClientData(joypad)); widgets::GetValidatedChild(panel, "DefaultConfig") ->SetValidator( - widgets::OptionSelectedValidator(config::OptionID::kJoyDefault, joypad + 1)); + widgets::OptionSelectedValidator(config::OptionID::kJoyDefault, joypad.ux_index())); // Set up tab order so input is easy to configure. Note that there are // two tabs for each panel, so we must check for the parent before @@ -43,7 +105,7 @@ JoypadConfig::JoypadConfig(wxWindow* parent) : BaseDialog(parent, "JoypadConfig" wxWindow* current_parent = game_key_control->GetParent(); game_key_control->SetValidator( - widgets::UserInputCtrlValidator(config::GameControl(joypad, game_key))); + UserInputCtrlValidator(config::GameCommand(joypad, game_key), bindings_provider)); if (current_parent == prev_parent) { // The first control will be skipped here, but that's fine since @@ -70,9 +132,11 @@ JoypadConfig::JoypadConfig(wxWindow* parent) : BaseDialog(parent, "JoypadConfig" } void JoypadConfig::ResetToDefaults(wxWindow* panel) { + const config::GameJoy& joypad = GameJoyClientData::From(panel); for (const config::GameKey& game_key : config::kAllGameKeys) { widgets::GetValidatedChild(panel, config::GameKeyToString(game_key)) - ->SetInputs(kDefaultBindings.find(config::GameControl(0, game_key))->second); + ->SetInputs(bindings_provider_()->DefaultInputsForCommand( + config::GameCommand(joypad, game_key))); } } diff --git a/src/wx/dialogs/joypad-config.h b/src/wx/dialogs/joypad-config.h index 6c8fbd5b..61687f20 100644 --- a/src/wx/dialogs/joypad-config.h +++ b/src/wx/dialogs/joypad-config.h @@ -1,21 +1,24 @@ #ifndef VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ #define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ +#include "wx/config/bindings.h" #include "wx/dialogs/base-dialog.h" namespace dialogs { // Manages the Joypad configuration dialog. +// Note that this dialog will silently overwrite shortcut assignments. class JoypadConfig : public BaseDialog { public: - static JoypadConfig* NewInstance(wxWindow* parent); + static JoypadConfig* NewInstance(wxWindow* parent, + const config::BindingsProvider bindings_provider); ~JoypadConfig() override = default; private: // The constructor is private so initialization has to be done via the // static method. This is because this class is destroyed when its // owner, `parent` is destroyed. This prevents accidental deletion. - JoypadConfig(wxWindow* parent); + JoypadConfig(wxWindow* parent, const config::BindingsProvider bindings_provider); // Resets all Joypad controls for `panel` to defaults. void ResetToDefaults(wxWindow* panel); @@ -28,6 +31,8 @@ private: // Toggle SDL GameController mode for all joysticks. void ToggleSDLGameControllerMode(); + + const config::BindingsProvider bindings_provider_; }; } // namespace dialogs diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp index 4d99258d..3299083a 100644 --- a/src/wx/guiinit.cpp +++ b/src/wx/guiinit.cpp @@ -2468,7 +2468,7 @@ bool MainFrame::BindControls() dialogs::DisplayConfig::NewInstance(this); dialogs::SoundConfig::NewInstance(this); dialogs::DirectoriesConfig::NewInstance(this); - dialogs::JoypadConfig::NewInstance(this); + dialogs::JoypadConfig::NewInstance(this, std::bind(&wxvbamApp::bindings, &wxGetApp())); #ifndef NO_LINK d = LoadXRCDialog("LinkConfig"); @@ -2481,7 +2481,7 @@ bool MainFrame::BindControls() } #endif dialogs::AccelConfig::NewInstance(this, menubar, recent, - std::bind(&wxvbamApp::shortcuts, &wxGetApp())); + std::bind(&wxvbamApp::bindings, &wxGetApp())); } catch (std::exception& e) { wxLogError(wxString::FromUTF8(e.what())); return false; diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp index c21b5611..c5cbbe16 100644 --- a/src/wx/opts.cpp +++ b/src/wx/opts.cpp @@ -10,12 +10,12 @@ #include #include +#include "wx/config/bindings.h" +#include "wx/config/command.h" #include "wx/config/option-observer.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" -#include "wx/config/shortcuts.h" #include "wx/config/user-input.h" -#include "wx/strutils.h" #include "wx/wxvbam.h" /* @@ -108,151 +108,6 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg, opts_t gopts; -const config::GameControlBindings kDefaultBindings = { - {config::GameControl(0, config::GameKey::Up), - { - config::KeyboardInput('W'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 11), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 1), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 3), - }}, - {config::GameControl(0, config::GameKey::Down), - { - config::KeyboardInput('S'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 12), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 1), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 3), - }}, - {config::GameControl(0, config::GameKey::Left), - { - config::KeyboardInput('A'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 13), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 0), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisMinus, 2), - }}, - {config::GameControl(0, config::GameKey::Right), - { - config::KeyboardInput('D'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 14), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 0), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 2), - }}, - {config::GameControl(0, config::GameKey::A), - { - config::KeyboardInput('L'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 0), - }}, - {config::GameControl(0, config::GameKey::B), - { - config::KeyboardInput('K'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 1), - }}, - {config::GameControl(0, config::GameKey::L), - { - config::KeyboardInput('I'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 2), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 9), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 4), - }}, - {config::GameControl(0, config::GameKey::R), - { - config::KeyboardInput('O'), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 3), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 10), - config::JoyInput(config::JoyId(0), config::JoyControl::AxisPlus, 5), - }}, - {config::GameControl(0, config::GameKey::Select), - { - config::KeyboardInput(WXK_BACK), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 4), - }}, - {config::GameControl(0, config::GameKey::Start), - { - config::KeyboardInput(WXK_RETURN), - config::JoyInput(config::JoyId(0), config::JoyControl::Button, 6), - }}, - {config::GameControl(0, config::GameKey::MotionUp), {}}, - {config::GameControl(0, config::GameKey::MotionDown), {}}, - {config::GameControl(0, config::GameKey::MotionLeft), {}}, - {config::GameControl(0, config::GameKey::MotionRight), {}}, - {config::GameControl(0, config::GameKey::MotionIn), {}}, - {config::GameControl(0, config::GameKey::MotionOut), {}}, - {config::GameControl(0, config::GameKey::AutoA), {}}, - {config::GameControl(0, config::GameKey::AutoB), {}}, - {config::GameControl(0, config::GameKey::Speed), - { - config::KeyboardInput(WXK_SPACE), - }}, - {config::GameControl(0, config::GameKey::Capture), {}}, - {config::GameControl(0, config::GameKey::Gameshark), {}}, - - {config::GameControl(1, config::GameKey::Up), {}}, - {config::GameControl(1, config::GameKey::Down), {}}, - {config::GameControl(1, config::GameKey::Left), {}}, - {config::GameControl(1, config::GameKey::Right), {}}, - {config::GameControl(1, config::GameKey::A), {}}, - {config::GameControl(1, config::GameKey::B), {}}, - {config::GameControl(1, config::GameKey::L), {}}, - {config::GameControl(1, config::GameKey::R), {}}, - {config::GameControl(1, config::GameKey::Select), {}}, - {config::GameControl(1, config::GameKey::Start), {}}, - {config::GameControl(1, config::GameKey::MotionUp), {}}, - {config::GameControl(1, config::GameKey::MotionDown), {}}, - {config::GameControl(1, config::GameKey::MotionLeft), {}}, - {config::GameControl(1, config::GameKey::MotionRight), {}}, - {config::GameControl(1, config::GameKey::MotionIn), {}}, - {config::GameControl(1, config::GameKey::MotionOut), {}}, - {config::GameControl(1, config::GameKey::AutoA), {}}, - {config::GameControl(1, config::GameKey::AutoB), {}}, - {config::GameControl(1, config::GameKey::Speed), {}}, - {config::GameControl(1, config::GameKey::Capture), {}}, - {config::GameControl(1, config::GameKey::Gameshark), {}}, - - {config::GameControl(2, config::GameKey::Up), {}}, - {config::GameControl(2, config::GameKey::Down), {}}, - {config::GameControl(2, config::GameKey::Left), {}}, - {config::GameControl(2, config::GameKey::Right), {}}, - {config::GameControl(2, config::GameKey::A), {}}, - {config::GameControl(2, config::GameKey::B), {}}, - {config::GameControl(2, config::GameKey::L), {}}, - {config::GameControl(2, config::GameKey::R), {}}, - {config::GameControl(2, config::GameKey::Select), {}}, - {config::GameControl(2, config::GameKey::Start), {}}, - {config::GameControl(2, config::GameKey::MotionUp), {}}, - {config::GameControl(2, config::GameKey::MotionDown), {}}, - {config::GameControl(2, config::GameKey::MotionLeft), {}}, - {config::GameControl(2, config::GameKey::MotionRight), {}}, - {config::GameControl(2, config::GameKey::MotionIn), {}}, - {config::GameControl(2, config::GameKey::MotionOut), {}}, - {config::GameControl(2, config::GameKey::AutoA), {}}, - {config::GameControl(2, config::GameKey::AutoB), {}}, - {config::GameControl(2, config::GameKey::Speed), {}}, - {config::GameControl(2, config::GameKey::Capture), {}}, - {config::GameControl(2, config::GameKey::Gameshark), {}}, - - {config::GameControl(3, config::GameKey::Up), {}}, - {config::GameControl(3, config::GameKey::Down), {}}, - {config::GameControl(3, config::GameKey::Left), {}}, - {config::GameControl(3, config::GameKey::Right), {}}, - {config::GameControl(3, config::GameKey::A), {}}, - {config::GameControl(3, config::GameKey::B), {}}, - {config::GameControl(3, config::GameKey::L), {}}, - {config::GameControl(3, config::GameKey::R), {}}, - {config::GameControl(3, config::GameKey::Select), {}}, - {config::GameControl(3, config::GameKey::Start), {}}, - {config::GameControl(3, config::GameKey::MotionUp), {}}, - {config::GameControl(3, config::GameKey::MotionDown), {}}, - {config::GameControl(3, config::GameKey::MotionLeft), {}}, - {config::GameControl(3, config::GameKey::MotionRight), {}}, - {config::GameControl(3, config::GameKey::MotionIn), {}}, - {config::GameControl(3, config::GameKey::MotionOut), {}}, - {config::GameControl(3, config::GameKey::AutoA), {}}, - {config::GameControl(3, config::GameKey::AutoB), {}}, - {config::GameControl(3, config::GameKey::Speed), {}}, - {config::GameControl(3, config::GameKey::Capture), {}}, - {config::GameControl(3, config::GameKey::Gameshark), {}}, -}; - // This constructor only works with globally allocated gopts. opts_t::opts_t() { @@ -469,29 +324,11 @@ void load_opts(bool first_time_launch) { } } - // Initialize game control bindings to populate the configuration map. - wxGetApp().game_control_bindings()->insert(kDefaultBindings.begin(), kDefaultBindings.end()); + config::Bindings* const bindings = wxGetApp().bindings(); - // joypad is special - for (auto& iter : *wxGetApp().game_control_bindings()) { - const wxString optname = iter.first.ToString(); - if (cfg->Read(optname, &s)) { - iter.second = config::UserInput::FromConfigString(s); - if (!s.empty() && iter.second.empty()) { - wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), optname.c_str()); - } - } else { - s = config::UserInput::SpanToConfigString(iter.second); - cfg->Write(optname, s); - } - } - - // keyboard is special // Keyboard does not get written with defaults wxString kbopt("Keyboard/"); int kboff = kbopt.size(); - config::Shortcuts* shortcuts = wxGetApp().shortcuts(); - for (int i = 0; i < ncmds; i++) { kbopt.resize(kboff); kbopt.append(cmdtab[i].cmd); @@ -502,12 +339,27 @@ void load_opts(bool first_time_launch) { wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), kbopt.c_str()); } else { for (const auto& input : inputs) { - shortcuts->AssignInputToCommand(input, cmdtab[i].cmd_id); + bindings->AssignInputToCommand(input, + config::ShortcutCommand(cmdtab[i].cmd_id)); } } } } + // Force overwrite the default Joypad configuration. + for (auto& iter : bindings->GetJoypadConfiguration()) { + const wxString optname = iter.first.ToConfigString(); + if (cfg->Read(optname, &s)) { + const auto user_inputs = config::UserInput::FromConfigString(s); + if (!s.empty() && user_inputs.empty()) { + wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), optname.c_str()); + } + bindings->AssignInputsToCommand(user_inputs, iter.first); + } else { + cfg->Write(optname, iter.second); + } + } + // recent is special // Recent does not get written with defaults cfg->SetPath(wxT("/Recent")); @@ -561,36 +413,13 @@ void update_opts() { } } -void update_joypad_opts() { - wxConfigBase* cfg = wxConfigBase::Get(); - - // For joypad, compare the UserInput sets. - bool game_bindings_changed = false; - for (const auto& iter : *wxvbamApp().game_control_bindings()) { - wxString option_name = iter.first.ToString(); - std::unordered_set saved_config = - config::UserInput::FromConfigString(cfg->Read(option_name, "")); - if (saved_config != iter.second) { - game_bindings_changed = true; - cfg->Write(option_name, config::UserInput::SpanToConfigString(iter.second)); - } - } - - if (game_bindings_changed) { - wxvbamApp().game_control_state()->OnGameBindingsChanged(); - } - - cfg->SetPath("/"); - cfg->Flush(); -} - void update_shortcut_opts() { wxConfigBase* cfg = wxConfigBase::Get(); - // For shortcuts, it's easier to delete everything and start over. + // For keyboard shortcuts, it's easier to delete everything and start over. cfg->DeleteGroup("/Keyboard"); cfg->SetPath("/Keyboard"); - for (const auto& iter : wxGetApp().shortcuts()->GetKeyboardConfiguration()) { + for (const auto& iter : wxGetApp().bindings()->GetKeyboardConfiguration()) { int cmd = 0; for (cmd = 0; cmd < ncmds; cmd++) if (cmdtab[cmd].cmd_id == iter.first) @@ -605,6 +434,22 @@ void update_shortcut_opts() { } cfg->SetPath("/"); + + // For joypads, we just compare the strings. + bool game_bindings_changed = false; + for (const auto& iter : wxGetApp().bindings()->GetJoypadConfiguration()) { + wxString option_name = iter.first.ToConfigString(); + wxString saved_config = cfg->Read(option_name, ""); + if (saved_config != iter.second) { + game_bindings_changed = true; + cfg->Write(option_name, iter.second); + } + } + + if (game_bindings_changed) { + wxGetApp().emulated_gamepad()->Reset(); + } + cfg->Flush(); } @@ -669,43 +514,16 @@ void opt_set(const wxString& name, const wxString& val) { } } - if (name.Find(wxT('/')) == wxNOT_FOUND) { + nonstd::optional command = config::Command::FromString(name); + if (command) { + config::Bindings* const bindings = wxGetApp().bindings(); + const auto inputs = config::UserInput::FromConfigString(val); + if (inputs.empty()) { + wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str()); + } + bindings->AssignInputsToCommand(inputs, *command); return; } - auto parts = strutils::split(name, wxT("/")); - if (parts[0] == wxT("Keyboard")) { - cmditem* cmd = std::lower_bound(&cmdtab[0], &cmdtab[ncmds], cmditem{parts[1],wxString(),0,0,NULL}, cmditem_lt); - - if (cmd == &cmdtab[ncmds] || wxStrcmp(parts[1], cmd->cmd)) { - return; - } - - if (!val.empty()) { - const auto inputs = config::UserInput::FromConfigString(val); - if (inputs.empty()) { - wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str()); - } - for (const auto& input : inputs) { - wxGetApp().shortcuts()->AssignInputToCommand(input, cmd->cmd_id); - } - } - - return; - } - - const nonstd::optional game_control = - config::GameControl::FromString(name); - auto game_control_bindings = wxGetApp().game_control_bindings(); - if (game_control) { - if (val.empty()) { - (*game_control_bindings)[game_control.value()].clear(); - } else { - (*game_control_bindings)[game_control.value()] = - config::UserInput::FromConfigString(val); - } - return; - } - - wxLogWarning(_("Unknown option %s with value %s"), name.c_str(), val.c_str()); + wxLogWarning(_("Unknown option %s with value %s"), name, val); } diff --git a/src/wx/opts.h b/src/wx/opts.h index 0905b9d5..cdbebec5 100644 --- a/src/wx/opts.h +++ b/src/wx/opts.h @@ -6,14 +6,9 @@ #include #include -#include "wx/config/game-control.h" - // Forward declaration. class wxFileHistory; -// Default joystick bindings. -extern const config::GameControlBindings kDefaultBindings; - extern struct opts_t { opts_t(); @@ -61,8 +56,6 @@ void load_opts(bool first_time_launch); // call whenever opt vars change // will detect changes and write config if necessary void update_opts(); -// Updates the joypad options. -void update_joypad_opts(); // Updates the shortcut options. void update_shortcut_opts(); // returns true if option name correct; prints error if val invalid diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index 69f3cd0b..5bfde2bb 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -41,7 +41,7 @@ #include "core/gba/gbaRtc.h" #include "core/gba/gbaSound.h" #include "wx/background-input.h" -#include "wx/config/game-control.h" +#include "wx/config/emulated-gamepad.h" #include "wx/config/option-id.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" @@ -1058,7 +1058,7 @@ GameArea::~GameArea() void GameArea::OnKillFocus(wxFocusEvent& ev) { - wxGetApp().game_control_state()->Reset(); + wxGetApp().emulated_gamepad()->Reset(); ev.Skip(); } @@ -1079,7 +1079,7 @@ void GameArea::Pause() // when the game is paused like this, we should not allow any // input to remain pressed, because they could be released // outside of the game zone and we would not know about it. - wxGetApp().game_control_state()->Reset(); + wxGetApp().emulated_gamepad()->Reset(); if (loaded != IMAGE_UNKNOWN) soundPause(); @@ -1330,13 +1330,13 @@ static Display* GetX11Display() { #endif // __WXGTK__ void GameArea::OnUserInputDown(widgets::UserInputEvent& event) { - if (wxGetApp().game_control_state()->OnInputPressed(event.input())) { + if (wxGetApp().emulated_gamepad()->OnInputPressed(event.input())) { wxWakeUpIdle(); } } void GameArea::OnUserInputUp(widgets::UserInputEvent& event) { - if (wxGetApp().game_control_state()->OnInputReleased(event.input())) { + if (wxGetApp().emulated_gamepad()->OnInputReleased(event.input())) { wxWakeUpIdle(); } @@ -1454,7 +1454,7 @@ DrawingPanel::DrawingPanel(wxWindow* parent, int _width, int _height) , wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { - this->SetClientData(new widgets::UserInputEventSender(this)); + this->SetClientObject(new widgets::UserInputEventSender(this)); } void DrawingPanelBase::DrawingPanelInit() @@ -2186,7 +2186,7 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height) , wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { - this->SetClientData(new widgets::UserInputEventSender(this)); + this->SetClientObject(new widgets::UserInputEventSender(this)); widgets::RequestHighResolutionOpenGlSurfaceForWindow(this); SetContext(); } diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index b70db8fc..ca1bb156 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -11,7 +11,7 @@ #include "core/gba/gbaGlobals.h" #include "core/gba/gbaSound.h" #include "wx/audio/audio.h" -#include "wx/config/game-control.h" +#include "wx/config/emulated-gamepad.h" #include "wx/config/option-proxy.h" #include "wx/wxvbam.h" @@ -340,7 +340,7 @@ uint32_t systemReadJoypad(int joy) if (joy < 0 || joy > 3) joy = OPTION(kJoyDefault) - 1; - uint32_t ret = wxGetApp().game_control_state()->GetJoypad(joy); + uint32_t ret = wxGetApp().emulated_gamepad()->GetJoypad(joy); if (turbo) ret |= KEYM_SPEED; @@ -662,7 +662,7 @@ void systemUpdateSolarSensor() void systemUpdateMotionSensor() { for (int i = 0; i < 4; i++) { - const uint32_t joy_value = wxGetApp().game_control_state()->GetJoypad(i); + const uint32_t joy_value = wxGetApp().emulated_gamepad()->GetJoypad(i); if (!sensorx[i]) sensorx[i] = 2047; diff --git a/src/wx/widgets/client-data.h b/src/wx/widgets/client-data.h new file mode 100644 index 00000000..6edf8537 --- /dev/null +++ b/src/wx/widgets/client-data.h @@ -0,0 +1,33 @@ +#ifndef VBAM_WX_WIDGETS_CLIENT_DATA_H_ +#define VBAM_WX_WIDGETS_CLIENT_DATA_H_ + +#include + +#include +#include + +namespace widgets { + +// A simple wxClientData subclass that holds a single piece of data. +template +class ClientData : public wxClientData { +public: + // Returns the data stored in the ClientData object. + static const T& From(wxWindow* window) { + wxClientData* data = window->GetClientObject(); + assert(data); + return static_cast*>(data)->data(); + } + + explicit ClientData(const T& data) : data_(data) {} + ~ClientData() override = default; + + const T& data() const { return data_; } + +private: + const T data_; +}; + +} // namespace widgets + +#endif // VBAM_WX_WIDGETS_CLIENT_DATA_H_ diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp index 47963de0..869b90c5 100644 --- a/src/wx/widgets/user-input-ctrl.cpp +++ b/src/wx/widgets/user-input-ctrl.cpp @@ -4,7 +4,6 @@ #include "wx/config/user-input.h" #include "wx/widgets/user-input-event.h" -#include "wx/wxvbam.h" namespace widgets { @@ -109,29 +108,6 @@ void UserInputCtrl::UpdateText() { } } -UserInputCtrlValidator::UserInputCtrlValidator(const config::GameControl game_control) : wxValidator(), game_control_(game_control) {} - -wxObject* UserInputCtrlValidator::Clone() const { - return new UserInputCtrlValidator(game_control_); -} - -bool UserInputCtrlValidator::TransferToWindow() { - UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl); - assert(control); - - control->SetInputs((*wxGetApp().game_control_bindings())[game_control_]); - return true; -} - -bool UserInputCtrlValidator::TransferFromWindow() { - UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl); - assert(control); - - wxGetApp().game_control_bindings()->insert({game_control_, control->inputs()}); - return true; -} - - UserInputCtrlXmlHandler::UserInputCtrlXmlHandler() : wxXmlResourceHandler() { AddWindowStyles(); } diff --git a/src/wx/widgets/user-input-ctrl.h b/src/wx/widgets/user-input-ctrl.h index 813751a1..eced50cc 100644 --- a/src/wx/widgets/user-input-ctrl.h +++ b/src/wx/widgets/user-input-ctrl.h @@ -6,10 +6,8 @@ #include #include #include -#include #include -#include "wx/config/game-control.h" #include "wx/config/user-input.h" #include "wx/widgets/user-input-event.h" @@ -81,24 +79,6 @@ private: std::unordered_set inputs_; }; -// A validator for the UserInputCtrl. This validator is used to transfer the -// GameControl data to and from the UserInputCtrl. -class UserInputCtrlValidator : public wxValidator { -public: - explicit UserInputCtrlValidator(const config::GameControl game_control); - ~UserInputCtrlValidator() override = default; - - wxObject* Clone() const override; - -protected: - // wxValidator implementation. - bool TransferToWindow() override; - bool TransferFromWindow() override; - bool Validate(wxWindow*) override { return true; } - - const config::GameControl game_control_; -}; - // Handler to load the resource from an XRC file as a "UserInputCtrl" object. class UserInputCtrlXmlHandler : public wxXmlResourceHandler { public: diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index 3aa8d930..3c875bb4 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -1,4 +1,5 @@ #include "wx/wxvbam.h" +#include "wx/config/command.h" #ifdef __WXMSW__ #include @@ -33,7 +34,7 @@ #include "core/gba/gbaSound.h" #include "wx/builtin-over.h" #include "wx/builtin-xrc.h" -#include "wx/config/game-control.h" +#include "wx/config/emulated-gamepad.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" #include "wx/config/user-input.h" @@ -59,7 +60,8 @@ void ResetMenuItemAccelerator(wxMenuItem* menu_item) { new_label.resize(tab_index); } std::unordered_set user_inputs = - wxGetApp().shortcuts()->InputsForCommand(menu_item->GetId()); + wxGetApp().bindings()->InputsForCommand( + config::ShortcutCommand(menu_item->GetId())); for (const config::UserInput& user_input : user_inputs) { if (user_input.device() != config::UserInput::Device::Keyboard) { // Cannot use joystick keybinding as text without wx assertion error. @@ -256,7 +258,7 @@ wxvbamApp::wxvbamApp() pending_fullscreen(false), frame(nullptr), using_wayland(false), - game_control_state_(std::bind(&wxvbamApp::game_control_bindings, this)), + emulated_gamepad_(std::bind(&wxvbamApp::bindings, this)), sdl_poller_(std::bind(&wxvbamApp::GetJoyEventHandler, this)) {} const wxString wxvbamApp::GetPluginsDir() @@ -540,10 +542,6 @@ bool wxvbamApp::OnInit() { } } - // Initialize game bindings here, after defaults bindings, vbam.ini bindings - // and command line overrides have been applied. - game_control_state()->OnGameBindingsChanged(); - // We need to gather this information before crating the MainFrame as the // OnSize / OnMove event handlers can fire during construction. const wxRect client_rect( @@ -1009,13 +1007,18 @@ int MainFrame::FilterEvent(wxEvent& event) { } const widgets::UserInputEvent& user_input_event = static_cast(event); - const int command = wxGetApp().shortcuts()->CommandForInput(user_input_event.input()); - if (command == 0) { + nonstd::optional command = + wxGetApp().bindings()->CommandForInput(user_input_event.input()); + if (command == nonstd::nullopt) { // No associated command found. return wxEventFilter::Event_Skip; } - wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command); + if (!command->is_shortcut()) { + return wxEventFilter::Event_Skip; + } + + wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command->shortcut().id()); command_event.SetEventObject(this); this->GetEventHandler()->ProcessEvent(command_event); return wxEventFilter::Event_Processed; @@ -1286,8 +1289,6 @@ LinkMode MainFrame::GetConfiguredLinkMode() return LINK_DISCONNECTED; break; } - - return LINK_DISCONNECTED; } #endif // NO_LINK diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 133faac0..ae13587e 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -12,10 +12,10 @@ #include #include "core/base/system.h" -#include "wx/config/game-control.h" +#include "wx/config/bindings.h" +#include "wx/config/emulated-gamepad.h" #include "wx/config/option-observer.h" #include "wx/config/option.h" -#include "wx/config/shortcuts.h" #include "wx/dialogs/base-dialog.h" #include "wx/widgets/dpi-support.h" #include "wx/widgets/keep-on-top-styler.h" @@ -59,18 +59,32 @@ inline std::string ToString(const wxChar* aString) class MainFrame; -class wxvbamApp : public wxApp { +class wxvbamApp final : public wxApp { public: wxvbamApp(); - virtual bool OnInit(); - virtual int OnRun(); - virtual bool OnCmdLineHelp(wxCmdLineParser&); - virtual bool OnCmdLineError(wxCmdLineParser&); - virtual bool UsingWayland() { return using_wayland; } - virtual void OnInitCmdLine(wxCmdLineParser&); - virtual bool OnCmdLineParsed(wxCmdLineParser&); - virtual wxString GetConfigDir(); - virtual wxString GetDataDir(); + + // wxApp implementation. + bool OnInit() final; + int OnRun() final; + bool OnCmdLineHelp(wxCmdLineParser&) final; + bool OnCmdLineError(wxCmdLineParser&) final; + void OnInitCmdLine(wxCmdLineParser&) final; + bool OnCmdLineParsed(wxCmdLineParser&) final; + // without this, global accels don't always work + int FilterEvent(wxEvent&) final; + // Handle most exceptions + bool OnExceptionInMainLoop() override { + try { + throw; + } catch (const std::exception& e) { + std::cerr << "AN ERROR HAS OCCURRED: " << e.what() << std::endl; + return false; + } + } + + wxString GetConfigDir(); + wxString GetDataDir(); + bool UsingWayland() { return using_wayland; } wxString GetConfigurationPath(); const wxString GetPluginsDir(); wxString GetAbsolutePath(wxString path); @@ -87,8 +101,6 @@ public: pending_load = f; }; #endif - // without this, global accels don't always work - int FilterEvent(wxEvent&); widgets::SdlPoller* sdl_poller() { return &sdl_poller_; } @@ -109,23 +121,11 @@ public: // there's no way to retrieve "current" locale, so this is public wxLocale locale; - // Handle most exceptions - virtual bool OnExceptionInMainLoop() - { - try { - throw; - } catch (const std::exception& e) { - std::cerr << "AN ERROR HAS OCCURRED: " << e.what() << std::endl; - return false; - } - } - // Accessors for configuration data. - config::Shortcuts* shortcuts() { return &shortcuts_; } - config::GameControlState* game_control_state() { return &game_control_state_; } - config::GameControlBindings* game_control_bindings() { return &game_control_bindings_; } + config::Bindings* bindings() { return &bindings_; } + config::EmulatedGamepad* emulated_gamepad() { return &emulated_gamepad_; } - virtual ~wxvbamApp(); + ~wxvbamApp() override; protected: bool using_wayland; @@ -136,9 +136,8 @@ private: // Returns the currently active event handler to use for user input events. wxEvtHandler* GetJoyEventHandler(); - config::Shortcuts shortcuts_; - config::GameControlState game_control_state_; - config::GameControlBindings game_control_bindings_; + config::Bindings bindings_; + config::EmulatedGamepad emulated_gamepad_; wxPathList config_path; char* home = nullptr; @@ -273,10 +272,7 @@ public: void ResetMenuAccelerators(); // 2.8 has no HasFocus(), and FindFocus() doesn't work right - bool HasFocus() const override - { - return focused; - } + bool HasFocus() const override { return focused; } #ifndef NO_LINK // Returns the link mode to set according to the options