[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:
Fabrice de Gans 2023-08-13 14:04:01 -07:00 committed by Fabrice de Gans
parent cfdbdc4ec2
commit 18a0067ca7
32 changed files with 2012 additions and 1263 deletions

View File

@ -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

View File

@ -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();
}
}

249
src/wx/config/bindings.cpp Normal file
View File

@ -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

105
src/wx/config/bindings.h Normal file
View File

@ -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_

172
src/wx/config/command.cpp Normal file
View File

@ -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

269
src/wx/config/command.h Normal file
View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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:

View File

@ -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,25 +67,26 @@ void AppendItemToTree(std::unordered_map<int, wxTreeItemId>* command_to_item_id,
}
assert(i < ncmds);
const wxTreeItemId tree_item_id =
tree->AppendItem(parent,
/*text=*/cmdtab[i].name,
/*image=*/-1,
/*selImage=*/-1,
/*data=*/
new CommandTreeItemData(
command, AppendString(prefix, level, cmdtab[i].name), cmdtab[i].name));
const wxTreeItemId tree_item_id = tree->AppendItem(
parent,
/*text=*/cmdtab[i].name,
/*image=*/-1,
/*selImage=*/-1,
/*data=*/
new CommandTreeItemData(config::ShortcutCommand(command),
AppendString(prefix, level, cmdtab[i].name), cmdtab[i].name));
command_to_item_id->emplace(command, tree_item_id);
}
// Built the initial tree control from the menu.
void PopulateTreeWithMenu(std::unordered_map<int, wxTreeItemId>* command_to_item_id,
wxTreeCtrl* tree,
const wxTreeItemId& parent,
wxMenu* menu,
const wxMenu* recents,
const wxString& prefix,
int level = 1) {
void PopulateTreeWithMenu(
std::unordered_map<config::ShortcutCommand, wxTreeItemId>* command_to_item_id,
wxTreeCtrl* tree,
const wxTreeItemId& parent,
wxMenu* menu,
const wxMenu* recents,
const wxString& prefix,
int level = 1) {
for (auto menu_item : menu->GetMenuItems()) {
if (menu_item->IsSeparator()) {
tree->AppendItem(parent, "-----");
@ -125,18 +118,18 @@ void PopulateTreeWithMenu(std::unordered_map<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);
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);
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;
}
const int confirmation =
wxMessageBox(wxString::Format(_("This will unassign \"%s\" from \"%s\". Are you sure?"),
user_input.ToLocalizedString(),
old_command_item_data->message_string()),
user_input.ToLocalizedString(), old_command_name),
_("Confirm"), wxYES_NO);
if (confirmation != wxYES) {
return;
@ -319,16 +321,24 @@ void AccelConfig::OnKeyInput(wxCommandEvent&) {
return;
}
const int command = config_shortcuts_.CommandForInput(user_input);
if (command == 0) {
const auto command = config_shortcuts_.CommandForInput(user_input);
if (!command) {
// No existing assignment.
currently_assigned_label_->SetLabel(wxEmptyString);
} else {
// Existing assignment, inform the user.
const auto iter = command_to_item_id_.find(command);
assert(iter != command_to_item_id_.end());
currently_assigned_label_->SetLabel(
static_cast<CommandTreeItemData*>(tree_->GetItemData(iter->second))->assigned_string());
switch (command->tag()) {
case config::Command::Tag::kGame:
currently_assigned_label_->SetLabel(command->game().ToUXString());
break;
case config::Command::Tag::kShortcut:
const auto iter = command_to_item_id_.find(command->shortcut());
assert(iter != command_to_item_id_.end());
currently_assigned_label_->SetLabel(
static_cast<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

View File

@ -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

View File

@ -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)));
}
}

View File

@ -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

View File

@ -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;

View File

@ -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) {
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());
}
bindings->AssignInputsToCommand(inputs, *command);
return;
}
auto parts = strutils::split(name, wxT("/"));
if (parts[0] == wxT("Keyboard")) {
cmditem* cmd = std::lower_bound(&cmdtab[0], &cmdtab[ncmds], cmditem{parts[1],wxString(),0,0,NULL}, cmditem_lt);
if (cmd == &cmdtab[ncmds] || wxStrcmp(parts[1], cmd->cmd)) {
return;
}
if (!val.empty()) {
const auto inputs = config::UserInput::FromConfigString(val);
if (inputs.empty()) {
wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str());
}
for (const auto& input : inputs) {
wxGetApp().shortcuts()->AssignInputToCommand(input, cmd->cmd_id);
}
}
return;
}
const nonstd::optional<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);
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;

View File

@ -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_

View File

@ -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();
}

View File

@ -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:

View File

@ -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

View File

@ -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