diff --git a/src/wx/config/game-control.cpp b/src/wx/config/game-control.cpp index efd1e022..ff524593 100644 --- a/src/wx/config/game-control.cpp +++ b/src/wx/config/game-control.cpp @@ -1,6 +1,5 @@ #include "wx/config/game-control.h" -#include "wx/opts.h" #include "wx/strutils.h" #include "wx/wxlogdebug.h" @@ -195,13 +194,8 @@ bool GameControl::operator>=(const GameControl& other) const { return !(*this < other); } -GameControlState& GameControlState::Instance() { - static GameControlState g_game_control_state; - return g_game_control_state; -} - -GameControlState::GameControlState() : joypads_({0, 0, 0, 0}) {} -GameControlState::~GameControlState() = default; +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); @@ -282,7 +276,7 @@ void GameControlState::OnGameBindingsChanged() { Reset(); input_bindings_.clear(); - for (const auto& iter : gopts.game_control_bindings) { + for (const auto& iter : *bindings_provider_()) { for (const auto& user_input : iter.second) { input_bindings_[user_input].emplace(iter.first); } diff --git a/src/wx/config/game-control.h b/src/wx/config/game-control.h index 5508f924..32350b3a 100644 --- a/src/wx/config/game-control.h +++ b/src/wx/config/game-control.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -107,12 +108,16 @@ private: friend class GameControlState; }; +using GameControlBindings = std::map>; +using GameControlBindingsProvider = std::function; + // Tracks in-game input and computes the joypad value used to send control input -// data to the emulator. -class GameControlState { +// data to the emulator. This class should be kept as a singleton owned by the +// application. +class GameControlState final { public: - // This is a global singleton. - static GameControlState& Instance(); + explicit GameControlState(const GameControlBindingsProvider bindings_provider); + ~GameControlState() = default; // Disable copy constructor and assignment operator. GameControlState(const GameControlState&) = delete; @@ -120,8 +125,8 @@ public: // Processes `user_input` and updates the internal tracking state. // Returns true if `user_input` corresponds to a game input. - bool OnInputPressed(const config::UserInput& user_input); - bool OnInputReleased(const config::UserInput& user_input); + bool OnInputPressed(const UserInput& user_input); + bool OnInputReleased(const UserInput& user_input); // Clears all input. void Reset(); @@ -133,15 +138,16 @@ public: uint32_t GetJoypad(int joypad) const; private: - GameControlState(); - ~GameControlState(); - std::map> input_bindings_; - std::map> active_controls_; - std::set keys_pressed_; + std::map> active_controls_; + std::unordered_set keys_pressed_; std::array joypads_; + + const GameControlBindingsProvider bindings_provider_; }; +using GameControlStateProvider = std::function; + } // namespace config #endif // VBAM_WX_CONFIG_GAME_CONTROL_H_ diff --git a/src/wx/config/internal/option-internal.cpp b/src/wx/config/internal/option-internal.cpp index 8181b470..8011ac9b 100644 --- a/src/wx/config/internal/option-internal.cpp +++ b/src/wx/config/internal/option-internal.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/src/wx/config/shortcuts.cpp b/src/wx/config/shortcuts.cpp index bebe5adf..a73bb843 100644 --- a/src/wx/config/shortcuts.cpp +++ b/src/wx/config/shortcuts.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "wx/config/user-input.h" @@ -28,14 +29,14 @@ Shortcuts::Shortcuts() { } } -Shortcuts::Shortcuts(const std::unordered_map>& command_to_inputs, - const std::map& input_to_command, - const std::map& disabled_defaults) +Shortcuts::Shortcuts(const std::unordered_map>& command_to_inputs, + const std::unordered_map& input_to_command, + const std::unordered_map& disabled_defaults) : command_to_inputs_(command_to_inputs.begin(), command_to_inputs.end()), input_to_command_(input_to_command.begin(), input_to_command.end()), disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {} -std::vector> Shortcuts::GetConfiguration() const { +std::vector> Shortcuts::GetKeyboardConfiguration() const { std::vector> config; config.reserve(command_to_inputs_.size() + 1); @@ -63,9 +64,9 @@ std::vector> Shortcuts::GetConfiguration() const { return config; } -std::set Shortcuts::InputsForCommand(int command) const { +std::unordered_set Shortcuts::InputsForCommand(int command) const { if (command == NoopCommand()) { - std::set noop_inputs; + std::unordered_set noop_inputs; for (const auto& iter : disabled_defaults_) { noop_inputs.insert(iter.first); } diff --git a/src/wx/config/shortcuts.h b/src/wx/config/shortcuts.h index 5ce61c14..994045b7 100644 --- a/src/wx/config/shortcuts.h +++ b/src/wx/config/shortcuts.h @@ -1,11 +1,10 @@ #ifndef VBAM_WX_CONFIG_SHORTCUTS_H_ #define VBAM_WX_CONFIG_SHORTCUTS_H_ -#include -#include -#include #include +#include #include +#include #include "wx/config/user-input.h" @@ -27,20 +26,23 @@ public: // Disable copy and copy assignment operator. // Clone() is provided only for the configuration window, this class - // should otherwise be treated as move-only. + // 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 configuration for the INI file. + // 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]. - std::vector> GetConfiguration() const; + // Essentially, this is a diff between the default shortcuts and the user + // configuration. + std::vector> GetKeyboardConfiguration() const; // Returns the list of input currently configured for `command`. - std::set InputsForCommand(int command) const; + std::unordered_set InputsForCommand(int command) const; // Returns the command currently assigned to `input` or nullptr if none. int CommandForInput(const UserInput& input) const; @@ -52,17 +54,17 @@ public: // Assigns `input` to `command`. Silently unassigns `input` if it is already // assigned to another command. - void AssignInputToCommand(const UserInput& input, int 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. + // must be a valid UserInput. Call will assert otherwise. void UnassignInput(const UserInput& input); private: - // Faster constructor for explitit copy. - Shortcuts(const std::unordered_map>& command_to_inputs, - const std::map& input_to_command, - const std::map& disabled_defaults); + // Faster constructor for explicit copy. + Shortcuts(const std::unordered_map>& command_to_inputs, + const std::unordered_map& input_to_command, + const std::unordered_map& disabled_defaults); // Helper method to unassign a binding used by the default configuration. // This requires special handling since the INI configuration is a diff @@ -70,14 +72,16 @@ private: void UnassignDefaultBinding(const UserInput& input); // Map of command to their associated input set. - std::unordered_map> command_to_inputs_; + std::unordered_map> command_to_inputs_; // Reverse map of the above. An input can only map to a single command. - std::map input_to_command_; - // Disabled default inputs. This is used to easily retrieve the + std::unordered_map input_to_command_; + // Disabled default shortcuts. This is used to easily retrieve the // configuration to save in the INI file. - std::map disabled_defaults_; + std::unordered_map disabled_defaults_; }; +using ShortcutsProvider = std::function; + } // namespace config #endif // VBAM_WX_CONFIG_SHORTCUTS_H_ diff --git a/src/wx/dialogs/accel-config.cpp b/src/wx/dialogs/accel-config.cpp index 104501cf..eedffccc 100644 --- a/src/wx/dialogs/accel-config.cpp +++ b/src/wx/dialogs/accel-config.cpp @@ -122,15 +122,21 @@ void PopulateTreeWithMenu(std::unordered_map* command_to_item } // namespace // static -AccelConfig* AccelConfig::NewInstance(wxWindow* parent, wxMenuBar* menu, wxMenu* recents) { +AccelConfig* AccelConfig::NewInstance(wxWindow* parent, + wxMenuBar* menu, + wxMenu* recents, + const config::ShortcutsProvider shortcuts_provider) { assert(parent); assert(menu); assert(recents); - return new AccelConfig(parent, menu, recents); + return new AccelConfig(parent, menu, recents, shortcuts_provider); } -AccelConfig::AccelConfig(wxWindow* parent, wxMenuBar* menu, wxMenu* recents) - : BaseDialog(parent, "AccelConfig") { +AccelConfig::AccelConfig(wxWindow* parent, + wxMenuBar* menu, + wxMenu* recents, + const config::ShortcutsProvider shortcuts_provider) + : BaseDialog(parent, "AccelConfig"), shortcuts_provider_(shortcuts_provider) { assert(menu); // Loads the various dialog elements. @@ -213,11 +219,11 @@ void AccelConfig::OnDialogShown(wxShowEvent& ev) { remove_button_->Enable(false); currently_assigned_label_->SetLabel(""); - config_shortcuts_ = gopts.shortcuts.Clone(); + config_shortcuts_ = shortcuts_provider_()->Clone(); } void AccelConfig::OnValidate(wxCommandEvent& ev) { - gopts.shortcuts = std::move(config_shortcuts_); + *shortcuts_provider_() = std::move(config_shortcuts_); ev.Skip(); } diff --git a/src/wx/dialogs/accel-config.h b/src/wx/dialogs/accel-config.h index 11c9ce42..cf16658e 100644 --- a/src/wx/dialogs/accel-config.h +++ b/src/wx/dialogs/accel-config.h @@ -24,7 +24,10 @@ namespace dialogs { // Manages the shortcuts editor dialog. class AccelConfig : public BaseDialog { public: - static AccelConfig* NewInstance(wxWindow* parent, wxMenuBar* menu_bar, wxMenu* recents); + static AccelConfig* NewInstance(wxWindow* parent, + wxMenuBar* menu_bar, + wxMenu* recents, + const config::ShortcutsProvider shortcuts_provider); ~AccelConfig() override = default; @@ -32,7 +35,10 @@ 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. - AccelConfig(wxWindow* parent, wxMenuBar* menu_bar, wxMenu* recents); + AccelConfig(wxWindow* parent, + wxMenuBar* menu_bar, + wxMenu* recents, + const config::ShortcutsProvider shortcuts_provider); // Re-initializes the configuration. void OnDialogShown(wxShowEvent& ev); @@ -73,6 +79,8 @@ private: config::Shortcuts config_shortcuts_; int selected_command_ = 0; + + const config::ShortcutsProvider shortcuts_provider_; }; } // namespace dialogs diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp index 6e506c48..4d99258d 100644 --- a/src/wx/guiinit.cpp +++ b/src/wx/guiinit.cpp @@ -2480,7 +2480,8 @@ bool MainFrame::BindControls() d->Fit(); } #endif - dialogs::AccelConfig::NewInstance(this, menubar, recent); + dialogs::AccelConfig::NewInstance(this, menubar, recent, + std::bind(&wxvbamApp::shortcuts, &wxGetApp())); } catch (std::exception& e) { wxLogError(wxString::FromUTF8(e.what())); return false; diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp index fefa1f43..c21b5611 100644 --- a/src/wx/opts.cpp +++ b/src/wx/opts.cpp @@ -13,6 +13,7 @@ #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" @@ -107,7 +108,7 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg, opts_t gopts; -const std::map> kDefaultBindings = { +const config::GameControlBindings kDefaultBindings = { {config::GameControl(0, config::GameKey::Up), { config::KeyboardInput('W'), @@ -469,10 +470,10 @@ void load_opts(bool first_time_launch) { } // Initialize game control bindings to populate the configuration map. - gopts.game_control_bindings.insert(kDefaultBindings.begin(), kDefaultBindings.end()); + wxGetApp().game_control_bindings()->insert(kDefaultBindings.begin(), kDefaultBindings.end()); // joypad is special - for (auto& iter : gopts.game_control_bindings) { + for (auto& iter : *wxGetApp().game_control_bindings()) { const wxString optname = iter.first.ToString(); if (cfg->Read(optname, &s)) { iter.second = config::UserInput::FromConfigString(s); @@ -487,8 +488,9 @@ void load_opts(bool first_time_launch) { // keyboard is special // Keyboard does not get written with defaults - wxString kbopt(wxT("Keyboard/")); + wxString kbopt("Keyboard/"); int kboff = kbopt.size(); + config::Shortcuts* shortcuts = wxGetApp().shortcuts(); for (int i = 0; i < ncmds; i++) { kbopt.resize(kboff); @@ -500,7 +502,7 @@ 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) { - gopts.shortcuts.AssignInputToCommand(input, cmdtab[i].cmd_id); + shortcuts->AssignInputToCommand(input, cmdtab[i].cmd_id); } } } @@ -564,7 +566,7 @@ void update_joypad_opts() { // For joypad, compare the UserInput sets. bool game_bindings_changed = false; - for (const auto &iter : gopts.game_control_bindings) { + for (const auto& iter : *wxvbamApp().game_control_bindings()) { wxString option_name = iter.first.ToString(); std::unordered_set saved_config = config::UserInput::FromConfigString(cfg->Read(option_name, "")); @@ -575,7 +577,7 @@ void update_joypad_opts() { } if (game_bindings_changed) { - config::GameControlState::Instance().OnGameBindingsChanged(); + wxvbamApp().game_control_state()->OnGameBindingsChanged(); } cfg->SetPath("/"); @@ -588,7 +590,7 @@ void update_shortcut_opts() { // For shortcuts, it's easier to delete everything and start over. cfg->DeleteGroup("/Keyboard"); cfg->SetPath("/Keyboard"); - for (const auto& iter : gopts.shortcuts.GetConfiguration()) { + for (const auto& iter : wxGetApp().shortcuts()->GetKeyboardConfiguration()) { int cmd = 0; for (cmd = 0; cmd < ncmds; cmd++) if (cmdtab[cmd].cmd_id == iter.first) @@ -685,7 +687,7 @@ void opt_set(const wxString& name, const wxString& val) { wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str()); } for (const auto& input : inputs) { - gopts.shortcuts.AssignInputToCommand(input, cmd->cmd_id); + wxGetApp().shortcuts()->AssignInputToCommand(input, cmd->cmd_id); } } @@ -694,11 +696,12 @@ void opt_set(const wxString& name, const wxString& val) { const nonstd::optional game_control = config::GameControl::FromString(name); + auto game_control_bindings = wxGetApp().game_control_bindings(); if (game_control) { if (val.empty()) { - gopts.game_control_bindings[game_control.value()].clear(); + (*game_control_bindings)[game_control.value()].clear(); } else { - gopts.game_control_bindings[game_control.value()] = + (*game_control_bindings)[game_control.value()] = config::UserInput::FromConfigString(val); } return; diff --git a/src/wx/opts.h b/src/wx/opts.h index a538c0c0..0905b9d5 100644 --- a/src/wx/opts.h +++ b/src/wx/opts.h @@ -1,21 +1,18 @@ #ifndef VBAM_WX_OPTS_H_ #define VBAM_WX_OPTS_H_ -#include +#include #include #include #include "wx/config/game-control.h" -#include "wx/config/shortcuts.h" -#include "wx/config/user-input.h" // Forward declaration. class wxFileHistory; // Default joystick bindings. -extern const std::map> - kDefaultBindings; +extern const config::GameControlBindings kDefaultBindings; extern struct opts_t { opts_t(); @@ -36,13 +33,8 @@ extern struct opts_t { int rewind_interval = 0; /// Joypad - std::map> - game_control_bindings; int autofire_rate = 1; - /// Keyboard - config::Shortcuts shortcuts; - /// Core int gdb_port = 55555; int link_num_players = 2; diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index 13c54763..69f3cd0b 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -1058,7 +1058,7 @@ GameArea::~GameArea() void GameArea::OnKillFocus(wxFocusEvent& ev) { - config::GameControlState::Instance().Reset(); + wxGetApp().game_control_state()->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. - config::GameControlState::Instance().Reset(); + wxGetApp().game_control_state()->Reset(); if (loaded != IMAGE_UNKNOWN) soundPause(); @@ -1330,13 +1330,13 @@ static Display* GetX11Display() { #endif // __WXGTK__ void GameArea::OnUserInputDown(widgets::UserInputEvent& event) { - if (config::GameControlState::Instance().OnInputPressed(event.input())) { + if (wxGetApp().game_control_state()->OnInputPressed(event.input())) { wxWakeUpIdle(); } } void GameArea::OnUserInputUp(widgets::UserInputEvent& event) { - if (config::GameControlState::Instance().OnInputReleased(event.input())) { + if (wxGetApp().game_control_state()->OnInputReleased(event.input())) { wxWakeUpIdle(); } diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index b435bc32..b70db8fc 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -340,7 +340,7 @@ uint32_t systemReadJoypad(int joy) if (joy < 0 || joy > 3) joy = OPTION(kJoyDefault) - 1; - uint32_t ret = config::GameControlState::Instance().GetJoypad(joy); + uint32_t ret = wxGetApp().game_control_state()->GetJoypad(joy); if (turbo) ret |= KEYM_SPEED; @@ -662,8 +662,7 @@ void systemUpdateSolarSensor() void systemUpdateMotionSensor() { for (int i = 0; i < 4; i++) { - const uint32_t joy_value = - config::GameControlState::Instance().GetJoypad(i); + const uint32_t joy_value = wxGetApp().game_control_state()->GetJoypad(i); if (!sensorx[i]) sensorx[i] = 2047; diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp index e4007e1e..47963de0 100644 --- a/src/wx/widgets/user-input-ctrl.cpp +++ b/src/wx/widgets/user-input-ctrl.cpp @@ -3,8 +3,8 @@ #include #include "wx/config/user-input.h" -#include "wx/opts.h" #include "wx/widgets/user-input-event.h" +#include "wx/wxvbam.h" namespace widgets { @@ -119,7 +119,7 @@ bool UserInputCtrlValidator::TransferToWindow() { UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl); assert(control); - control->SetInputs(gopts.game_control_bindings[game_control_]); + control->SetInputs((*wxGetApp().game_control_bindings())[game_control_]); return true; } @@ -127,7 +127,7 @@ bool UserInputCtrlValidator::TransferFromWindow() { UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl); assert(control); - gopts.game_control_bindings.insert({game_control_, control->inputs()}); + wxGetApp().game_control_bindings()->insert({game_control_, control->inputs()}); return true; } diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index 461715c9..3aa8d930 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -58,8 +58,8 @@ void ResetMenuItemAccelerator(wxMenuItem* menu_item) { if (tab_index != wxString::npos) { new_label.resize(tab_index); } - std::set user_inputs = - gopts.shortcuts.InputsForCommand(menu_item->GetId()); + std::unordered_set user_inputs = + wxGetApp().shortcuts()->InputsForCommand(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 +256,8 @@ wxvbamApp::wxvbamApp() pending_fullscreen(false), frame(nullptr), using_wayland(false), - sdl_poller_(widgets::SdlPoller(std::bind(&wxvbamApp::GetJoyEventHandler, this))) {} + game_control_state_(std::bind(&wxvbamApp::game_control_bindings, this)), + sdl_poller_(std::bind(&wxvbamApp::GetJoyEventHandler, this)) {} const wxString wxvbamApp::GetPluginsDir() { @@ -541,7 +542,7 @@ bool wxvbamApp::OnInit() { // Initialize game bindings here, after defaults bindings, vbam.ini bindings // and command line overrides have been applied. - config::GameControlState::Instance().OnGameBindingsChanged(); + game_control_state()->OnGameBindingsChanged(); // We need to gather this information before crating the MainFrame as the // OnSize / OnMove event handlers can fire during construction. @@ -1008,7 +1009,7 @@ int MainFrame::FilterEvent(wxEvent& event) { } const widgets::UserInputEvent& user_input_event = static_cast(event); - const int command = gopts.shortcuts.CommandForInput(user_input_event.input()); + const int command = wxGetApp().shortcuts()->CommandForInput(user_input_event.input()); if (command == 0) { // No associated command found. return wxEventFilter::Event_Skip; diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index db1197ba..133faac0 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -12,8 +12,10 @@ #include #include "core/base/system.h" +#include "wx/config/game-control.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" @@ -118,6 +120,11 @@ public: } } + // 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_; } + virtual ~wxvbamApp(); protected: @@ -129,6 +136,10 @@ 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_; + wxPathList config_path; char* home = nullptr;