diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 5d798f79..8d936774 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -773,6 +773,7 @@ set( dialogs/game-boy-config.cpp dialogs/game-maker.cpp dialogs/gb-rom-info.cpp + dialogs/joypad-config.cpp widgets/group-check-box.cpp widgets/keep-on-top-styler.cpp widgets/option-validator.cpp @@ -826,6 +827,7 @@ set( dialogs/game-boy-config.h dialogs/game-maker.h dialogs/gb-rom-info.h + dialogs/joypad-config.h dialogs/validated-child.h widgets/dpi-support.h widgets/group-check-box.h diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index 792f43cc..d3370c06 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -2227,18 +2227,22 @@ EVT_HANDLER(EmulatorDirectories, "Directories...") EVT_HANDLER(JoypadConfigure, "Joypad options...") { - wxDialog* dlg = GetXRCDialog("JoypadConfig"); joy.PollAllJoysticks(); auto frame = wxGetApp().frame; bool joy_timer = frame->IsJoyPollTimerRunning(); - if (!joy_timer) frame->StartJoyPollTimer(); + if (!joy_timer) { + frame->StartJoyPollTimer(); + } - if (ShowModal(dlg) == wxID_OK) - update_opts(); + if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) { + update_joypad_opts(); + } - if (!joy_timer) frame->StopJoyPollTimer(); + if (!joy_timer) { + frame->StopJoyPollTimer(); + } SetJoystick(); } diff --git a/src/wx/config/internal/option-internal.cpp b/src/wx/config/internal/option-internal.cpp index 009db2a0..bb729d51 100644 --- a/src/wx/config/internal/option-internal.cpp +++ b/src/wx/config/internal/option-internal.cpp @@ -209,6 +209,9 @@ std::array& Option::All() { bool statusbar = false; uint32_t ini_version = kIniLatestVersion; + /// Joypad + uint32_t default_stick = 1; + /// Geometry bool fullscreen = false; bool window_maximized = false; @@ -289,7 +292,7 @@ std::array& Option::All() { /// Joypad Option(OptionID::kJoy), Option(OptionID::kJoyAutofireThrottle, &gopts.autofire_rate, 1, 1000), - Option(OptionID::kJoyDefault, &gopts.default_stick, 1, 4), + Option(OptionID::kJoyDefault, &g_owned_opts.default_stick, 1, 4), /// Keyboard Option(OptionID::kKeyboard), diff --git a/src/wx/config/option-proxy.h b/src/wx/config/option-proxy.h index abc251df..367162d4 100644 --- a/src/wx/config/option-proxy.h +++ b/src/wx/config/option-proxy.h @@ -64,7 +64,7 @@ static constexpr std::array kOptionsTypes = { /// Joypad /*kJoy*/ Option::Type::kNone, /*kJoyAutofireThrottle*/ Option::Type::kInt, - /*kJoyDefault*/ Option::Type::kInt, + /*kJoyDefault*/ Option::Type::kUnsigned, /// Keyboard /*kKeyboard*/ Option::Type::kNone, diff --git a/src/wx/dialogs/joypad-config.cpp b/src/wx/dialogs/joypad-config.cpp new file mode 100644 index 00000000..82286e19 --- /dev/null +++ b/src/wx/dialogs/joypad-config.cpp @@ -0,0 +1,85 @@ +#include "dialogs/joypad-config.h" + +#include + +#include "dialogs/validated-child.h" +#include "opts.h" +#include "widgets/option-validator.h" +#include "widgets/user-input-ctrl.h" + +namespace dialogs { + +// static +JoypadConfig* JoypadConfig::NewInstance(wxWindow* parent) { + assert(parent); + return new JoypadConfig(parent); +} + +JoypadConfig::JoypadConfig(wxWindow* parent) : wxDialog(), keep_on_top_styler_(this) { +#if !wxCHECK_VERSION(3, 1, 0) + // This needs to be set before loading any element on the window. This also + // has no effect since wx 3.1.0, where it became the default. + this->SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY); +#endif + wxXmlResource::Get()->LoadDialog(this, parent, "JoypadConfig"); + + for (int joypad = 0; joypad < 4; joypad++) { + wxWindow* panel = GetValidatedChild(this, wxString::Format("joy%d", joypad + 1)); + + GetValidatedChild(panel, "DefaultConfig") + ->SetValidator( + widgets::OptionSelectedValidator(config::OptionID::kJoyDefault, joypad + 1)); + + // 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 + // setting up the tab order. + wxWindow* prev = nullptr; + wxWindow* prev_parent = nullptr; + for (const config::GameKey& game_key : config::kAllGameKeys) { + const wxString game_key_name = config::GameKeyToString(game_key); + widgets::UserInputCtrl* game_key_control = + GetValidatedChild(panel, game_key_name); + wxWindow* current_parent = game_key_control->GetParent(); + + game_key_control->SetValidator( + widgets::UserInputCtrlValidator(config::GameControl(joypad, game_key))); + + if (current_parent == prev_parent) { + // The first control will be skipped here, but that's fine since + // we don't care where it fits in the tab order. + assert(prev); + game_key_control->MoveAfterInTabOrder(prev); + } + prev = game_key_control; + prev_parent = current_parent; + + // Bind the individual "Clear" key event. + panel->Bind(wxEVT_BUTTON, std::bind(&widgets::UserInputCtrl::Clear, game_key_control), + XRCID(wxString("Clear" + config::GameKeyToString(game_key)).c_str())); + } + + // Finally, bind the per-joypad "Defaults" and "Clear" events. + panel->Bind(wxEVT_BUTTON, std::bind(&JoypadConfig::ResetToDefaults, this, panel), + XRCID("Defaults")); + panel->Bind(wxEVT_BUTTON, std::bind(&JoypadConfig::ClearJoypad, this, panel), + XRCID("Clear")); + } + + this->Fit(); +} + +void JoypadConfig::ResetToDefaults(wxWindow* panel) { + for (const config::GameKey& game_key : config::kAllGameKeys) { + GetValidatedChild(panel, config::GameKeyToString(game_key)) + ->SetInputs(kDefaultBindings.find(config::GameControl(0, game_key))->second); + } +} + +void JoypadConfig::ClearJoypad(wxWindow* panel) { + for (const config::GameKey& game_key : config::kAllGameKeys) { + GetValidatedChild(panel, config::GameKeyToString(game_key)) + ->Clear(); + } +} + +} // namespace dialogs diff --git a/src/wx/dialogs/joypad-config.h b/src/wx/dialogs/joypad-config.h new file mode 100644 index 00000000..b19aeb6f --- /dev/null +++ b/src/wx/dialogs/joypad-config.h @@ -0,0 +1,33 @@ +#ifndef VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ +#define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ + +#include + +#include "widgets/keep-on-top-styler.h" + +namespace dialogs { + +// Manages the Joypad configuration dialog. +class JoypadConfig : public wxDialog { +public: + static JoypadConfig* NewInstance(wxWindow* parent); + ~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); + + // Resets all Joypad controls for `panel` to defaults. + void ResetToDefaults(wxWindow* panel); + + // Clears all Joypad controls. + void ClearJoypad(wxWindow* panel); + + const widgets::KeepOnTopStyler keep_on_top_styler_; +}; + +} // namespace dialogs + +#endif // VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp index f8557191..8667b197 100644 --- a/src/wx/guiinit.cpp +++ b/src/wx/guiinit.cpp @@ -43,6 +43,7 @@ #include "dialogs/display-config.h" #include "dialogs/game-boy-config.h" #include "dialogs/gb-rom-info.h" +#include "dialogs/joypad-config.h" #include "opts.h" #include "widgets/option-validator.h" #include "widgets/user-input-ctrl.h" @@ -1604,40 +1605,6 @@ public: } }; -// manage the joypad prefs' per-panel default/clear buttons -static class JoyPadConfig_t : public wxEvtHandler { -public: - wxWindow* p; - void JoypadConfigButtons(wxCommandEvent& ev) - { - bool clear = ev.GetId() == XRCID("Clear"); - - // For the individual clear buttons, we assume their name is - // "Clear" + control_name - // ClearUp for Up; ClearR for R etc - for (const config::GameKey& game_key : config::kAllGameKeys) { - const wxString control_name = config::GameKeyToString(game_key); - widgets::UserInputCtrl* tc = XRCCTRL_D(*p, control_name, widgets::UserInputCtrl); - wxString singleClearButton("Clear" + control_name); - if (ev.GetId() == XRCID(singleClearButton.c_str())) { - tc->Clear(); - return; - } - } - - for (const config::GameKey& game_key : config::kAllGameKeys) { - widgets::UserInputCtrl* tc = - XRCCTRL_D(*p, config::GameKeyToString(game_key), widgets::UserInputCtrl); - - if (clear) { - tc->Clear(); - } else { - tc->SetInputs(kDefaultBindings.find(config::GameControl(0, game_key))->second); - } - } - } -} JoyPadConfigHandler[4]; - // manage throttle spinctrl/canned setting choice interaction static class ThrottleCtrl_t : public wxEvtHandler { public: @@ -2775,54 +2742,7 @@ bool MainFrame::BindControls() d->Fit(); } dialogs::DirectoriesConfig::NewInstance(this); - wxDialog* joyDialog = LoadXRCropertySheetDialog("JoypadConfig"); - - for (int i = 0; i < 4; i++) { - wxString pn; - // NOTE: wx2.9.1 behaves differently for referenced nodes - // than 2.8! Unless there is an actual child node, the ID field - // will not be overwritten. This means that there should be a - // dummy child node (e.g. position=(0,0)). If you get - // "Unable to load dialog JoypadConfig from resources", this is - // probably the reason. - pn.Printf(wxT("joy%d"), i + 1); - wxWindow* w = SafeXRCCTRL(joyDialog, pn); - - w->FindWindow("DefaultConfig") - ->SetValidator(wxBoolIntValidator(&gopts.default_stick, i + 1)); - wxWindow *prev = NULL, *prevp = NULL; - - for (const config::GameKey& game_key : config::kAllGameKeys) { - const wxString control_name = config::GameKeyToString(game_key); - widgets::UserInputCtrl* tc = XRCCTRL_D(*w, control_name, widgets::UserInputCtrl); - CheckThrowXRCError(tc, control_name); - wxWindow* p = tc->GetParent(); - - if (p == prevp) - tc->MoveAfterInTabOrder(prev); - - prev = tc; - prevp = p; - tc->SetValidator(widgets::UserInputCtrlValidator(config::GameControl(i, game_key))); - } - - JoyPadConfigHandler[i].p = w; - w->Connect(XRCID("Defaults"), wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons), - NULL, &JoyPadConfigHandler[i]); - w->Connect(XRCID("Clear"), wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons), - NULL, &JoyPadConfigHandler[i]); - for (const config::GameKey& game_key : config::kAllGameKeys) { - const wxString control_name = config::GameKeyToString(game_key); - w->Connect(XRCID(wxString("Clear" + control_name).c_str()), - wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons), - NULL, &JoyPadConfigHandler[i]); - } - - joyDialog->Fit(); - } + dialogs::JoypadConfig::NewInstance(this); #ifndef NO_LINK d = LoadXRCDialog("LinkConfig"); diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp index 9d6b6fa9..b5a64902 100644 --- a/src/wx/opts.cpp +++ b/src/wx/opts.cpp @@ -531,11 +531,13 @@ void load_opts(bool first_time_launch) { // Note: run load_opts() first to guarantee all config opts exist void update_opts() { - wxConfigBase* cfg = wxConfigBase::Get(); - for (config::Option& opt : config::Option::All()) { SaveOption(&opt); } +} + +void update_joypad_opts() { + wxConfigBase* cfg = wxConfigBase::Get(); // For joypad, compare the UserInput sets. Since UserInput guarantees a // certain ordering, it is possible that the user control in the panel shows @@ -551,6 +553,7 @@ void update_opts() { cfg->Write(option_name, config::UserInput::SpanToConfigString(iter.second)); } } + if (game_bindings_changed) { config::GameControlState::Instance().OnGameBindingsChanged(); } diff --git a/src/wx/opts.h b/src/wx/opts.h index 815b3956..6f05bce4 100644 --- a/src/wx/opts.h +++ b/src/wx/opts.h @@ -42,7 +42,6 @@ extern struct opts_t { std::map> game_control_bindings; int autofire_rate = 1; - int default_stick = 1; /// Keyboard config::Shortcuts shortcuts; @@ -85,6 +84,8 @@ void load_opts(bool first_time_launch); // call whenever opt vars change // will detect changes and write config if necessary void update_opts(); +// Updates the joypad options. +void update_joypad_opts(); // Updates the shortcut options. void update_shortcut_opts(); // returns true if option name correct; prints error if val invalid diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index 961ab788..b37afe19 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -334,7 +334,7 @@ bool systemReadJoypads() uint32_t systemReadJoypad(int joy) { if (joy < 0 || joy > 3) - joy = gopts.default_stick - 1; + joy = OPTION(kJoyDefault) - 1; uint32_t ret = config::GameControlState::Instance().GetJoypad(joy); @@ -772,17 +772,17 @@ void systemUpdateMotionSensor() int systemGetSensorX() { - return sensorx[gopts.default_stick - 1]; + return sensorx[OPTION(kJoyDefault) - 1]; } int systemGetSensorY() { - return sensory[gopts.default_stick - 1]; + return sensory[OPTION(kJoyDefault) - 1]; } int systemGetSensorZ() { - return sensorz[gopts.default_stick - 1] / 10; + return sensorz[OPTION(kJoyDefault) - 1] / 10; } class PrintDialog : public wxEvtHandler, public wxPrintout {