diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp index 91c042d5b4be..1af80b18d7cf 100644 --- a/Common/Data/Format/IniFile.cpp +++ b/Common/Data/Format/IniFile.cpp @@ -717,6 +717,18 @@ bool IniFile::Get(const char* sectionName, const char* key, bool* value, bool de } } +bool IniFile::Get(const char* sectionName, const char* key, float* value, float defaultValue) +{ + Section *section = GetSection(sectionName); + if (!section) { + *value = defaultValue; + return false; + } else { + return section->Get(key, value, defaultValue); + } +} + + // Unit test. TODO: Move to the real unit test framework. /* diff --git a/Common/Data/Format/IniFile.h b/Common/Data/Format/IniFile.h index 1008fff5df94..83a005c865f1 100644 --- a/Common/Data/Format/IniFile.h +++ b/Common/Data/Format/IniFile.h @@ -125,6 +125,7 @@ class IniFile { bool Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue = 0); bool Get(const char* sectionName, const char* key, uint64_t* value, uint64_t defaultValue = 0); bool Get(const char* sectionName, const char* key, bool* value, bool defaultValue = false); + bool Get(const char* sectionName, const char* key, float* value, float defaultValue = 0.0f); bool Get(const char* sectionName, const char* key, std::vector& values); template bool GetIfExists(const char* sectionName, const char* key, T value) diff --git a/Core/Config.cpp b/Core/Config.cpp index 1debc4692036..5a32673ca2e6 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -547,21 +547,6 @@ static const ConfigSetting generalSettings[] = { ConfigSetting("GridView1", &g_Config.bGridView1, true), ConfigSetting("GridView2", &g_Config.bGridView2, true), ConfigSetting("GridView3", &g_Config.bGridView3, false), - ConfigSetting("RightAnalogUp", &g_Config.iRightAnalogUp, 0, true, true), - ConfigSetting("RightAnalogDown", &g_Config.iRightAnalogDown, 0, true, true), - ConfigSetting("RightAnalogLeft", &g_Config.iRightAnalogLeft, 0, true, true), - ConfigSetting("RightAnalogRight", &g_Config.iRightAnalogRight, 0, true, true), - ConfigSetting("RightAnalogPress", &g_Config.iRightAnalogPress, 0, true, true), - ConfigSetting("RightAnalogCustom", &g_Config.bRightAnalogCustom, false, true, true), - ConfigSetting("RightAnalogDisableDiagonal", &g_Config.bRightAnalogDisableDiagonal, false, true, true), - ConfigSetting("SwipeUp", &g_Config.iSwipeUp, 0, true, true), - ConfigSetting("SwipeDown", &g_Config.iSwipeDown, 0, true, true), - ConfigSetting("SwipeLeft", &g_Config.iSwipeLeft, 0, true, true), - ConfigSetting("SwipeRight", &g_Config.iSwipeRight, 0, true, true), - ConfigSetting("SwipeSensitivity", &g_Config.fSwipeSensitivity, 1.0f, true, true), - ConfigSetting("SwipeSmoothing", &g_Config.fSwipeSmoothing, 0.3f, true, true), - ConfigSetting("DoubleTapGesture", &g_Config.iDoubleTapGesture, 0, true, true), - ConfigSetting("GestureControlEnabled", &g_Config.bGestureControlEnabled, false, true, true), // "default" means let emulator decide, "" means disable. ConfigSetting("ReportingHost", &g_Config.sReportHost, "default"), @@ -606,6 +591,7 @@ static const ConfigSetting generalSettings[] = { ConfigSetting("EnablePlugins", &g_Config.bLoadPlugins, true, true, true), ReportedConfigSetting("IgnoreCompatSettings", &g_Config.sIgnoreCompatSettings, "", true, true), + ConfigSetting("SettingsVersion", &g_Config.uSettingsVersion, 0u, true, true), // Per game for game configs }; static bool DefaultSasThread() { @@ -1039,6 +1025,23 @@ static const ConfigSetting controlSettings[] = { ConfigSetting("MouseSensitivity", &g_Config.fMouseSensitivity, 0.1f, true, true), ConfigSetting("MouseSmoothing", &g_Config.fMouseSmoothing, 0.9f, true, true), + ConfigSetting("RightAnalogUp", &g_Config.iRightAnalogUp, 0, true, true), + ConfigSetting("RightAnalogDown", &g_Config.iRightAnalogDown, 0, true, true), + ConfigSetting("RightAnalogLeft", &g_Config.iRightAnalogLeft, 0, true, true), + ConfigSetting("RightAnalogRight", &g_Config.iRightAnalogRight, 0, true, true), + ConfigSetting("RightAnalogPress", &g_Config.iRightAnalogPress, 0, true, true), + ConfigSetting("RightAnalogCustom", &g_Config.bRightAnalogCustom, false, true, true), + ConfigSetting("RightAnalogDisableDiagonal", &g_Config.bRightAnalogDisableDiagonal, false, true, true), + + ConfigSetting("SwipeUp", &g_Config.iSwipeUp, 0, true, true), + ConfigSetting("SwipeDown", &g_Config.iSwipeDown, 0, true, true), + ConfigSetting("SwipeLeft", &g_Config.iSwipeLeft, 0, true, true), + ConfigSetting("SwipeRight", &g_Config.iSwipeRight, 0, true, true), + ConfigSetting("SwipeSensitivity", &g_Config.fSwipeSensitivity, 1.0f, true, true), + ConfigSetting("SwipeSmoothing", &g_Config.fSwipeSmoothing, 0.3f, true, true), + ConfigSetting("DoubleTapGesture", &g_Config.iDoubleTapGesture, 0, true, true), + ConfigSetting("GestureControlEnabled", &g_Config.bGestureControlEnabled, false, true, true), + ConfigSetting("SystemControls", &g_Config.bSystemControls, true, true, false), }; @@ -1315,6 +1318,24 @@ void Config::UpdateAfterSettingAutoFrameSkip() { } } +static void loadOldControlSettings(IniFile &iniFile) { + iniFile.GetIfExists("General", "RightAnalogUp", &g_Config.iRightAnalogUp); + iniFile.GetIfExists("General", "RightAnalogDown", &g_Config.iRightAnalogDown); + iniFile.GetIfExists("General", "RightAnalogLeft", &g_Config.iRightAnalogLeft); + iniFile.GetIfExists("General", "RightAnalogRight", &g_Config.iRightAnalogRight); + iniFile.GetIfExists("General", "RightAnalogPress", &g_Config.iRightAnalogPress); + iniFile.GetIfExists("General", "SwipeUp", &g_Config.iSwipeUp); + iniFile.GetIfExists("General", "SwipeDown", &g_Config.iSwipeDown); + iniFile.GetIfExists("General", "SwipeLeft", &g_Config.iSwipeLeft); + iniFile.GetIfExists("General", "SwipeRight", &g_Config.iSwipeRight); + iniFile.GetIfExists("General", "DoubleTapGesture", &g_Config.iDoubleTapGesture); + iniFile.GetIfExists("General", "SwipeSensitivity", &g_Config.fSwipeSensitivity); + iniFile.GetIfExists("General", "SwipeSmoothing", &g_Config.fSwipeSmoothing); + iniFile.GetIfExists("General", "RightAnalogCustom", &g_Config.bRightAnalogCustom); + iniFile.GetIfExists("General", "RightAnalogDisableDiagonal", &g_Config.bRightAnalogDisableDiagonal); + iniFile.GetIfExists("General", "GestureControlEnabled", &g_Config.bGestureControlEnabled); +} + void Config::Load(const char *iniFileName, const char *controllerIniFilename) { if (!bUpdatedInstanceCounter) { InitInstanceCounter(); @@ -1419,6 +1440,12 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { ResetControlLayout(); } + if (g_Config.uSettingsVersion == 0) { + g_Config.uSettingsVersion = 1; + + loadOldControlSettings(iniFile); + } + const char *gitVer = PPSSPP_GIT_VERSION; Version installed(gitVer); Version upgrade(upgradeVersion); @@ -1466,6 +1493,58 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { INFO_LOG(LOADER, "Config loaded: '%s'", iniFilename_.c_str()); } +bool Config::SaveControllerProfile(uint32_t id) { + IniFile iniFile; + if (!iniFile.Load(controllerIniFilename_)) { + ERROR_LOG(LOADER, "Error saving controller profile - can't read ini '%s'", controllerIniFilename_.c_str()); + return false; + } + + Section *section = iniFile.GetOrCreateSection(StringFromFormat("ControlProfile%uSettings", id).c_str()); + for (auto &setting : controlSettings) { + setting.Set(section); + } + + KeyMap::SaveToIni(iniFile, StringFromFormat("ControlProfile%uMapping", id).c_str()); + + if (!iniFile.Save(controllerIniFilename_)) { + ERROR_LOG(LOADER, "Error saving controller profile - can't write ini '%s'", controllerIniFilename_.c_str()); + return false; + } + + return true; +} + +bool Config::ControllerProfileExist(uint32_t id) { + IniFile iniFile; + if (!iniFile.Load(controllerIniFilename_)) { + ERROR_LOG(LOADER, "Error checking controller profile - can't read ini '%s'", controllerIniFilename_.c_str()); + return false; + } + + return iniFile.HasSection(StringFromFormat("ControlProfile%uSettings", id).c_str()); +} + +bool Config::LoadControllerProfile(uint32_t id) { + IniFile iniFile; + if (!iniFile.Load(controllerIniFilename_)) { + ERROR_LOG(LOADER, "Error loading controller profile - can't read ini '%s'", controllerIniFilename_.c_str()); + return false; + } + + if (!iniFile.HasSection(StringFromFormat("ControlProfile%uSettings", id).c_str())) + return false; + + Section *section = iniFile.GetOrCreateSection(StringFromFormat("ControlProfile%uSettings", id).c_str()); + for (auto &setting : controlSettings) { + setting.Get(section); + } + + KeyMap::LoadFromIni(iniFile, StringFromFormat("ControlProfile%uMapping", id).c_str()); + + return true; +} + bool Config::Save(const char *saveReason) { if (!IsFirstInstance()) { // TODO: Should we allow saving config if started from a different directory? @@ -1944,6 +2023,12 @@ bool Config::loadGameConfig(const std::string &pGameId, const std::string &title } }); + if (g_Config.uSettingsVersion == 0) { + g_Config.uSettingsVersion = 1; + + loadOldControlSettings(iniFile); + } + KeyMap::LoadFromIni(iniFile); if (!appendedConfigFileName_.ToString().empty() && diff --git a/Core/Config.h b/Core/Config.h index 17c5197a9712..797c15edf8bd 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -90,6 +90,7 @@ struct Config { bool bEnableLogging; bool bDumpDecryptedEboot; bool bFullscreenOnDoubleclick; + uint32_t uSettingsVersion; // These four are Win UI only bool bPauseOnLostFocus; @@ -521,6 +522,9 @@ struct Config { void Load(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr); bool Save(const char *saveReason); + bool SaveControllerProfile(uint32_t id); + bool LoadControllerProfile(uint32_t id); + bool ControllerProfileExist(uint32_t id); void Reload(); void RestoreDefaults(RestoreSettingsBits whatToRestore); diff --git a/Core/KeyMap.cpp b/Core/KeyMap.cpp index bdb50d98b7af..47ff2e1242ef 100644 --- a/Core/KeyMap.cpp +++ b/Core/KeyMap.cpp @@ -432,6 +432,12 @@ const KeyMap_IntStrPair psp_button_names[] = { {CTRL_VOL_DOWN, "Vol -"}, {CTRL_SCREEN, "Screen"}, {CTRL_NOTE, "Note"}, + + {VIRTKEY_CONTROLLER_PROFILE_1, "Load control profile 1"}, + {VIRTKEY_CONTROLLER_PROFILE_2, "Load control profile 2"}, + {VIRTKEY_CONTROLLER_PROFILE_3, "Load control profile 3"}, + {VIRTKEY_CONTROLLER_PROFILE_4, "Load control profile 4"}, + {VIRTKEY_CONTROLLER_PROFILE_5, "Load control profile 5"}, }; static std::string FindName(int key, const KeyMap_IntStrPair list[], size_t size) { @@ -685,13 +691,13 @@ void RestoreDefault() { } // TODO: Make the ini format nicer. -void LoadFromIni(IniFile &file) { +void LoadFromIni(IniFile &file, const char *section) { RestoreDefault(); - if (!file.HasSection("ControlMapping")) { + if (!file.HasSection("ControlMapping")) { // Only on default section return; } - Section *controls = file.GetOrCreateSection("ControlMapping"); + Section *controls = file.GetOrCreateSection(section); for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) { std::string value; controls->Get(psp_button_names[i].name, &value, ""); @@ -717,8 +723,8 @@ void LoadFromIni(IniFile &file) { UpdateNativeMenuKeys(); } -void SaveToIni(IniFile &file) { - Section *controls = file.GetOrCreateSection("ControlMapping"); +void SaveToIni(IniFile &file, const char *section) { + Section *controls = file.GetOrCreateSection(section); for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) { std::vector keys; diff --git a/Core/KeyMap.h b/Core/KeyMap.h index 139cbaab3ce9..259c2f22615c 100644 --- a/Core/KeyMap.h +++ b/Core/KeyMap.h @@ -71,6 +71,11 @@ enum { VIRTKEY_SPEED_ANALOG = 0x40000024, VIRTKEY_VR_CAMERA_ADJUST = 0x40000025, VIRTKEY_VR_CAMERA_RESET = 0x40000026, + VIRTKEY_CONTROLLER_PROFILE_1 = 0x40000027, + VIRTKEY_CONTROLLER_PROFILE_2 = 0x40000028, + VIRTKEY_CONTROLLER_PROFILE_3 = 0x40000029, + VIRTKEY_CONTROLLER_PROFILE_4 = 0x4000002A, + VIRTKEY_CONTROLLER_PROFILE_5 = 0x4000002B, VIRTKEY_LAST, VIRTKEY_COUNT = VIRTKEY_LAST - VIRTKEY_FIRST }; @@ -190,8 +195,8 @@ namespace KeyMap { MappedAnalogAxes MappedAxesForDevice(int deviceId); - void LoadFromIni(IniFile &iniFile); - void SaveToIni(IniFile &iniFile); + void LoadFromIni(IniFile &iniFile, const char *section = "ControlMapping"); + void SaveToIni(IniFile &iniFile, const char *section = "ControlMapping"); void ClearAllMappings(); void DeleteNthMapping(int key, int number); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 22bf7bf76827..3afb2ebeb792 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -754,6 +754,26 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) { if (down) g_Config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180; break; + case VIRTKEY_CONTROLLER_PROFILE_1: + g_Config.LoadControllerProfile(1); + RecreateViews(); + break; + case VIRTKEY_CONTROLLER_PROFILE_2: + g_Config.LoadControllerProfile(2); + RecreateViews(); + break; + case VIRTKEY_CONTROLLER_PROFILE_3: + g_Config.LoadControllerProfile(3); + RecreateViews(); + break; + case VIRTKEY_CONTROLLER_PROFILE_4: + g_Config.LoadControllerProfile(4); + RecreateViews(); + break; + case VIRTKEY_CONTROLLER_PROFILE_5: + g_Config.LoadControllerProfile(5); + RecreateViews(); + break; } } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 563d1f24f2d8..66cc2f726668 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -686,6 +686,11 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings) controlsSettings->Add(new Choice(co->T("Control Mapping")))->OnClick.Handle(this, &GameSettingsScreen::OnControlMapping); controlsSettings->Add(new Choice(co->T("Calibrate Analog Stick")))->OnClick.Handle(this, &GameSettingsScreen::OnCalibrateAnalogs); + controlsSettings->Add(new Choice(co->T("Control Profiles")))->OnClick.Add([=](EventParams &e) { + screenManager()->push(new ControllerProfileScreen()); + return UI::EVENT_DONE; + }); + #if defined(USING_WIN_UI) controlsSettings->Add(new CheckBox(&g_Config.bSystemControls, co->T("Enable standard shortcut keys"))); controlsSettings->Add(new CheckBox(&g_Config.bGamepadOnlyFocused, co->T("Ignore gamepads when not focused"))); @@ -2241,3 +2246,55 @@ void RestoreSettingsScreen::OnCompleted(DialogResult result) { g_Config.RestoreDefaults((RestoreSettingsBits)restoreFlags_); } } +void ControllerProfileScreen::CreateViews() { + using namespace UI; + + auto co = GetI18NCategory("Controls"); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + AddStandardBack(root_); + TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false)); + root_->Add(tabHolder); + ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL); + tabHolder->AddTab(co->T("Profiles"), rightPanel); + LinearLayout *vert = rightPanel->Add(new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); + vert->SetSpacing(0); + + float leftSide = 40.0f; + if (g_display.dp_yres <= g_display.dp_xres * 1.1f) { + leftSide += 200.0f; + } + settingInfo_ = new SettingInfoMessage(ALIGN_CENTER | FLAG_WRAP_TEXT, new AnchorLayoutParams(g_display.dp_xres - leftSide - 40.0f, WRAP_CONTENT, leftSide, g_display.dp_yres - 80.0f - 40.0f, NONE, NONE)); + settingInfo_->SetBottomCutoff(g_display.dp_yres - 200.0f); + root_->Add(settingInfo_); + + static constexpr int MAX_PROFILE = 5; // Should we have 5 with binding for fast selection and unlimited just to have them? + for (int i = 1; i <= MAX_PROFILE; ++i) { + vert->Add(new ItemHeader(ReplaceAll(co->T("Profile %1"), "%1", std::to_string(i)))); + + Choice *save = new Choice(co->T("Save current control settings")); + Choice *load = new Choice(co->T("Load control profile")); + + load->OnClick.Add([=](EventParams &e) { + if (g_Config.LoadControllerProfile(i)) + settingInfo_->Show(co->T("Control profile loaded"), e.v); + + return UI::EVENT_CONTINUE; + }); + + save->OnClick.Add([=](EventParams &e) { + if (g_Config.SaveControllerProfile(i)) { + settingInfo_->Show(co->T("Control profile saved"), e.v); + load->SetEnabled(true); + } + + return UI::EVENT_CONTINUE; + }); + + // Not a func to avoid Android slow IO problems + load->SetEnabled(g_Config.ControllerProfileExist(i)); + vert->Add(save); + vert->Add(load); + } +} + diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 89f1ce23ad06..324fefa9810c 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -265,3 +265,12 @@ class RestoreSettingsScreen : public PopupScreen { void OnCompleted(DialogResult result) override; int restoreFlags_ = (int)(RestoreSettingsBits::SETTINGS); // RestoreSettingsBits enum }; + +class ControllerProfileScreen : public UIDialogScreenWithBackground { +public: + void CreateViews() override; + + const char *tag() const override { return "ControllerProfile"; } +private: + SettingInfoMessage *settingInfo_; +}; diff --git a/UI/GamepadEmu.h b/UI/GamepadEmu.h index 9236038a6838..d76683bd8f67 100644 --- a/UI/GamepadEmu.h +++ b/UI/GamepadEmu.h @@ -329,6 +329,11 @@ namespace CustomKeyData { { ImageID::invalid(), VIRTKEY_AXIS_Y_MIN }, { ImageID::invalid(), VIRTKEY_AXIS_X_MAX }, { ImageID::invalid(), VIRTKEY_AXIS_Y_MAX }, + { ImageID::invalid(), VIRTKEY_CONTROLLER_PROFILE_1 }, + { ImageID::invalid(), VIRTKEY_CONTROLLER_PROFILE_2 }, + { ImageID::invalid(), VIRTKEY_CONTROLLER_PROFILE_3 }, + { ImageID::invalid(), VIRTKEY_CONTROLLER_PROFILE_4 }, + { ImageID::invalid(), VIRTKEY_CONTROLLER_PROFILE_5 }, }; static_assert(ARRAY_SIZE(customKeyList) <= 64, "Too many key for a uint64_t bit mask"); }; @@ -371,5 +376,10 @@ namespace GestureKey { VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_X_MAX, VIRTKEY_AXIS_Y_MAX, + VIRTKEY_CONTROLLER_PROFILE_1, + VIRTKEY_CONTROLLER_PROFILE_2, + VIRTKEY_CONTROLLER_PROFILE_3, + VIRTKEY_CONTROLLER_PROFILE_4, + VIRTKEY_CONTROLLER_PROFILE_5, }; }