[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
This commit is contained in:
parent
cfdbdc4ec2
commit
18a0067ca7
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
#include "wx/config/bindings.h"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#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<UserInput>& 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<Command, std::unordered_set<UserInput>>& control_to_inputs,
|
||||
const std::unordered_map<UserInput, Command>& input_to_control,
|
||||
const std::unordered_map<UserInput, ShortcutCommand>& 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<std::pair<int, wxString>> Bindings::GetKeyboardConfiguration() const {
|
||||
std::vector<std::pair<int, wxString>> config;
|
||||
config.reserve(control_to_inputs_.size() + 1);
|
||||
|
||||
if (!disabled_defaults_.empty()) {
|
||||
std::unordered_set<UserInput> 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<UserInput> 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<std::pair<GameCommand, wxString>> Bindings::GetJoypadConfiguration() const {
|
||||
std::vector<std::pair<GameCommand, wxString>> 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<UserInput>& inputs = iter->second;
|
||||
config.push_back(std::make_pair(game_command, UserInput::SpanToConfigString(inputs)));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::unordered_set<UserInput> Bindings::InputsForCommand(const Command& command) const {
|
||||
if (command.is_shortcut() && command.shortcut().id() == NoopCommand()) {
|
||||
std::unordered_set<UserInput> 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<Command> 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<UserInput>& 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<UserInput> 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
|
|
@ -0,0 +1,105 @@
|
|||
#ifndef VBAM_WX_CONFIG_BINDINGS_H_
|
||||
#define VBAM_WX_CONFIG_BINDINGS_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<UserInput>& 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<std::pair<int, wxString>> GetKeyboardConfiguration() const;
|
||||
|
||||
// Returns the game control configuration for the INI file. These go in the
|
||||
// [Joypad] section of the INI file.
|
||||
std::vector<std::pair<GameCommand, wxString>> GetJoypadConfiguration() const;
|
||||
|
||||
// Returns the list of input currently configured for `command`.
|
||||
std::unordered_set<UserInput> InputsForCommand(const Command& command) const;
|
||||
|
||||
// Returns the Command currently assigned to `input` or nullopt if none.
|
||||
nonstd::optional<Command> 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<UserInput>& 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<Command, std::unordered_set<UserInput>>& control_to_inputs,
|
||||
const std::unordered_map<UserInput, Command>& input_to_control,
|
||||
const std::unordered_map<UserInput, ShortcutCommand>& 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, std::unordered_set<UserInput>> control_to_inputs_;
|
||||
// Reverse map of the above. An input can only map to a single command.
|
||||
std::unordered_map<UserInput, Command> input_to_control_;
|
||||
// Disabled default shortcuts. This is used to easily retrieve the
|
||||
// configuration to save in the INI file.
|
||||
std::unordered_map<UserInput, ShortcutCommand> disabled_defaults_;
|
||||
};
|
||||
|
||||
using BindingsProvider = std::function<Bindings*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_BINDINGS_H_
|
|
@ -0,0 +1,172 @@
|
|||
#include "wx/config/command.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <wx/wxchar.h>
|
||||
|
||||
#include "wx/strutils.h"
|
||||
#include "wx/wxvbam.h"
|
||||
|
||||
namespace config {
|
||||
namespace {
|
||||
|
||||
constexpr int GameKeyToInt(const GameKey& game_key) {
|
||||
return static_cast<int>(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<size_t>(joypad) >= kMinJoypadIndex &&
|
||||
static_cast<size_t>(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<wxString, kNbGameKeys> 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<wxString, kNbGameKeys> 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<GameKey> StringToGameKey(const wxString& input) {
|
||||
static const std::map<wxString, GameKey> 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> 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<GameKey> 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
|
|
@ -0,0 +1,269 @@
|
|||
#ifndef VBAM_WX_CONFIG_COMMAND_H_
|
||||
#define VBAM_WX_CONFIG_COMMAND_H_
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
|
||||
#include <optional.hpp>
|
||||
#include <variant.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
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<size_t>(GameKey::Last) + 1;
|
||||
|
||||
static constexpr std::array<GameKey, config::kNbGameKeys> 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<GameJoy, config::kNbJoypads> 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<GameKey> 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<Command> 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<GameCommand>(control_);
|
||||
}
|
||||
|
||||
const ShortcutCommand& shortcut() const {
|
||||
assert(is_shortcut());
|
||||
return nonstd::get<ShortcutCommand>(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<GameCommand, ShortcutCommand> control_;
|
||||
};
|
||||
|
||||
} // namespace config
|
||||
|
||||
// Specializations for hash functions for all of the above classes.
|
||||
template <>
|
||||
struct std::hash<config::GameKey> {
|
||||
std::size_t operator()(const config::GameKey& game_key) const noexcept {
|
||||
return std::hash<size_t>{}(static_cast<size_t>(game_key));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::GameJoy> {
|
||||
std::size_t operator()(const config::GameJoy& game_joy) const noexcept {
|
||||
return std::hash<size_t>{}(static_cast<size_t>(game_joy.index()));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::GameCommand> {
|
||||
std::size_t operator()(const config::GameCommand& game_control) const noexcept {
|
||||
const std::size_t hash1 = std::hash<config::GameJoy>{}(game_control.joypad());
|
||||
const std::size_t hash2 = std::hash<config::GameKey>{}(game_control.game_key());
|
||||
return hash1 ^ hash2;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::ShortcutCommand> {
|
||||
std::size_t operator()(const config::ShortcutCommand& shortcut) const noexcept {
|
||||
return std::hash<int>{}(shortcut.id());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::Command> {
|
||||
std::size_t operator()(const config::Command& control) const noexcept {
|
||||
switch (control.tag()) {
|
||||
case config::Command::Tag::kGame:
|
||||
return std::hash<config::GameCommand>{}(control.game());
|
||||
case config::Command::Tag::kShortcut:
|
||||
return std::hash<config::ShortcutCommand>{}(control.shortcut());
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // VBAM_WX_CONFIG_COMMAND_H_
|
|
@ -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<uint32_t, kNbGameKeys> 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<size_t>(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<UserInput>()))
|
||||
.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
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
||||
#define VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#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<GameCommand, std::unordered_set<UserInput>> active_controls_;
|
||||
std::array<uint32_t, kNbJoypads> joypads_;
|
||||
const BindingsProvider bindings_provider_;
|
||||
};
|
||||
|
||||
using EmulatedGamepadProvider = std::function<EmulatedGamepad*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
|
@ -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<uint32_t, kNbGameKeys> 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<int>(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<wxString, kNbGameKeys> 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<GameKey> StringToGameKey(const wxString& input) {
|
||||
static const std::map<wxString, GameKey> 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> 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<GameKey> 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
|
|
@ -1,153 +0,0 @@
|
|||
#ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
#define VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <optional.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#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<size_t>(GameKey::Last) + 1;
|
||||
inline constexpr int kNbJoypads = 4;
|
||||
|
||||
inline constexpr std::array<GameKey, kNbGameKeys> 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<GameKey> 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<GameControl> 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<GameControl, std::unordered_set<UserInput>>;
|
||||
using GameControlBindingsProvider = std::function<GameControlBindings*()>;
|
||||
|
||||
// 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<config::UserInput, std::set<GameControl>> input_bindings_;
|
||||
std::map<GameControl, std::unordered_set<UserInput>> active_controls_;
|
||||
std::unordered_set<config::UserInput> keys_pressed_;
|
||||
std::array<uint32_t, kNbJoypads> joypads_;
|
||||
|
||||
const GameControlBindingsProvider bindings_provider_;
|
||||
};
|
||||
|
||||
using GameControlStateProvider = std::function<GameControlState*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_GAME_CONTROL_H_
|
|
@ -0,0 +1,615 @@
|
|||
#include "wx/config/bindings.h"
|
||||
#include "wx/config/command.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#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<Command, std::unordered_set<UserInput>>& DefaultInputs() {
|
||||
// clang-format off
|
||||
static const std::unordered_map<Command, std::unordered_set<UserInput>> 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<UserInput>& DefaultInputsForCommand(const Command& command) {
|
||||
const auto& iter = DefaultInputs().find(command);
|
||||
if (iter != DefaultInputs().end()) {
|
||||
return iter->second;
|
||||
}
|
||||
static const std::unordered_set<UserInput> 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
|
|
@ -0,0 +1,118 @@
|
|||
#ifndef VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
#error "Do not include "config/internal/bindings-internal.h" outside of the implementation."
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<Command, std::unordered_set<UserInput>>& 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<UserInput>& 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<GameCommand, kNbGameKeys * kNbJoypads> 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
|
|
@ -1,108 +0,0 @@
|
|||
#include "wx/config/shortcuts.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#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<int, UserInput>& DefaultShortcuts() {
|
||||
static const std::unordered_map<int, UserInput> 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
|
|
@ -1,21 +0,0 @@
|
|||
#ifndef VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
#error "Do not include "config/internal/shortcuts-internal.h" outside of the implementation."
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace config {
|
||||
namespace internal {
|
||||
|
||||
// Returns the map of commands to their default shortcut.
|
||||
const std::unordered_map<int, UserInput>& 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
|
|
@ -1,174 +0,0 @@
|
|||
#include "wx/config/shortcuts.h"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#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<int, std::unordered_set<UserInput>>& command_to_inputs,
|
||||
const std::unordered_map<UserInput, int>& input_to_command,
|
||||
const std::unordered_map<UserInput, int>& 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<std::pair<int, wxString>> Shortcuts::GetKeyboardConfiguration() const {
|
||||
std::vector<std::pair<int, wxString>> config;
|
||||
config.reserve(command_to_inputs_.size() + 1);
|
||||
|
||||
if (!disabled_defaults_.empty()) {
|
||||
std::unordered_set<UserInput> 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<UserInput> 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<UserInput> Shortcuts::InputsForCommand(int command) const {
|
||||
if (command == NoopCommand()) {
|
||||
std::unordered_set<UserInput> 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
|
|
@ -1,87 +0,0 @@
|
|||
#ifndef VBAM_WX_CONFIG_SHORTCUTS_H_
|
||||
#define VBAM_WX_CONFIG_SHORTCUTS_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<std::pair<int, wxString>> GetKeyboardConfiguration() const;
|
||||
|
||||
// Returns the list of input currently configured for `command`.
|
||||
std::unordered_set<UserInput> 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<int, std::unordered_set<UserInput>>& command_to_inputs,
|
||||
const std::unordered_map<UserInput, int>& input_to_command,
|
||||
const std::unordered_map<UserInput, int>& 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<int, std::unordered_set<UserInput>> command_to_inputs_;
|
||||
// Reverse map of the above. An input can only map to a single command.
|
||||
std::unordered_map<UserInput, int> input_to_command_;
|
||||
// Disabled default shortcuts. This is used to easily retrieve the
|
||||
// configuration to save in the INI file.
|
||||
std::unordered_map<UserInput, int> disabled_defaults_;
|
||||
};
|
||||
|
||||
using ShortcutsProvider = std::function<Shortcuts*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_SHORTCUTS_H_
|
|
@ -207,14 +207,14 @@ private:
|
|||
// Specializations for hash functions for all of the above classes.
|
||||
template <>
|
||||
struct std::hash<config::JoyId> {
|
||||
std::size_t operator()(config::JoyId const& joy_id) const noexcept {
|
||||
std::size_t operator()(const config::JoyId& joy_id) const noexcept {
|
||||
return std::hash<int>{}(joy_id.sdl_index_);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::JoyInput> {
|
||||
std::size_t operator()(config::JoyInput const& joy_input) const noexcept {
|
||||
std::size_t operator()(const config::JoyInput& joy_input) const noexcept {
|
||||
const std::size_t hash1 = std::hash<config::JoyId>{}(joy_input.joy());
|
||||
const std::size_t hash2 = std::hash<int>{}(static_cast<int>(joy_input.control()));
|
||||
const std::size_t hash3 = std::hash<int>{}(joy_input.control_index());
|
||||
|
@ -224,7 +224,7 @@ struct std::hash<config::JoyInput> {
|
|||
|
||||
template <>
|
||||
struct std::hash<config::KeyboardInput> {
|
||||
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<int>{}(keyboard_input.key());
|
||||
const std::size_t hash2 = std::hash<int>{}(keyboard_input.mod());
|
||||
return hash1 ^ hash2;
|
||||
|
@ -233,7 +233,7 @@ struct std::hash<config::KeyboardInput> {
|
|||
|
||||
template <>
|
||||
struct std::hash<config::UserInput> {
|
||||
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<int>{}(static_cast<int>(user_input.device()));
|
||||
switch (user_input.device()) {
|
||||
case config::UserInput::Device::Invalid:
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
#include <wx/menu.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#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<config::UserInput>;
|
||||
|
||||
// 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<int, wxTreeItemId>* command_to_item_id,
|
||||
void AppendItemToTree(std::unordered_map<config::ShortcutCommand, wxTreeItemId>* command_to_item_id,
|
||||
wxTreeCtrl* tree,
|
||||
const wxTreeItemId& parent,
|
||||
int command,
|
||||
|
@ -75,19 +67,20 @@ void AppendItemToTree(std::unordered_map<int, wxTreeItemId>* command_to_item_id,
|
|||
}
|
||||
assert(i < ncmds);
|
||||
|
||||
const wxTreeItemId tree_item_id =
|
||||
tree->AppendItem(parent,
|
||||
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));
|
||||
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<int, wxTreeItemId>* command_to_item_id,
|
||||
void PopulateTreeWithMenu(
|
||||
std::unordered_map<config::ShortcutCommand, wxTreeItemId>* command_to_item_id,
|
||||
wxTreeCtrl* tree,
|
||||
const wxTreeItemId& parent,
|
||||
wxMenu* menu,
|
||||
|
@ -125,18 +118,18 @@ void PopulateTreeWithMenu(std::unordered_map<int, wxTreeItemId>* 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<UserInputClientData*>(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);
|
||||
const nonstd::optional<config::Command> 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<const CommandTreeItemData*>(tree_->GetItemData(iter->second));
|
||||
assert(old_command_item_data);
|
||||
old_command_name = old_command_item_data->message_string();
|
||||
break;
|
||||
}
|
||||
|
||||
// Require user confirmation to override.
|
||||
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);
|
||||
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<CommandTreeItemData*>(tree_->GetItemData(iter->second))->assigned_string());
|
||||
static_cast<CommandTreeItemData*>(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
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#include <wx/treectrl.h>
|
||||
|
||||
#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<int, wxTreeItemId> command_to_item_id_;
|
||||
std::unordered_map<config::ShortcutCommand, wxTreeItemId> 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
|
||||
|
|
|
@ -2,34 +2,96 @@
|
|||
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#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<config::GameJoy>;
|
||||
|
||||
// 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<wxCheckBox>("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<widgets::UserInputCtrl>(panel, config::GameKeyToString(game_key))
|
||||
->SetInputs(kDefaultBindings.find(config::GameControl(0, game_key))->second);
|
||||
->SetInputs(bindings_provider_()->DefaultInputsForCommand(
|
||||
config::GameCommand(joypad, game_key)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
266
src/wx/opts.cpp
266
src/wx/opts.cpp
|
@ -10,12 +10,12 @@
|
|||
#include <wx/log.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#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<config::UserInput> 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) {
|
||||
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()) {
|
||||
nonstd::optional<config::Command> 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());
|
||||
}
|
||||
for (const auto& input : inputs) {
|
||||
wxGetApp().shortcuts()->AssignInputToCommand(input, cmd->cmd_id);
|
||||
}
|
||||
}
|
||||
|
||||
bindings->AssignInputsToCommand(inputs, *command);
|
||||
return;
|
||||
}
|
||||
|
||||
const nonstd::optional<config::GameControl> 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);
|
||||
}
|
||||
|
|
|
@ -6,14 +6,9 @@
|
|||
#include <wx/string.h>
|
||||
#include <wx/vidmode.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef VBAM_WX_WIDGETS_CLIENT_DATA_H_
|
||||
#define VBAM_WX_WIDGETS_CLIENT_DATA_H_
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <wx/clntdata.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
namespace widgets {
|
||||
|
||||
// A simple wxClientData subclass that holds a single piece of data.
|
||||
template <typename T>
|
||||
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<ClientData<T>*>(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_
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
#include <wx/longlong.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/validate.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#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<config::UserInput> 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:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "wx/wxvbam.h"
|
||||
#include "wx/config/command.h"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <windows.h>
|
||||
|
@ -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<config::UserInput> 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<widgets::UserInputEvent&>(event);
|
||||
const int command = wxGetApp().shortcuts()->CommandForInput(user_input_event.input());
|
||||
if (command == 0) {
|
||||
nonstd::optional<config::Command> 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
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
#include <wx/datetime.h>
|
||||
|
||||
#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
|
||||
|
|
Loading…
Reference in New Issue