Settings: Move the System tab's builder to UI/Settings/SystemPage.cpp

Like the other tabs, except that the screen keeps what the page edits that
isn't a config setting (the compatibility reports flag, which is written
back when the screen closes, and the Windows memstick checkboxes' state) in
a SystemPageState it passes in. The page's handlers move with it, as
functions taking the page host or that state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-25 17:23:10 +01:00
1 parent b570c96d68
commit 0ed9c8542b
10 files changed
+590 -524

No files matched your search

+5 -495
View File
@@ -123,17 +123,11 @@ GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID,
g_Config.LoadGameConfig(gameID_);
}
ReadConfigTemporaries();
}
// Also called after restoring defaults, so the old values aren't written back over them.
void GameSettingsScreen::ReadConfigTemporaries() {
// Written back on exit, so it must be read even if the System tab is never opened.
enableReports_ = Reporting::IsEnabled();
ReadSystemPageState(&systemState_);
}
GameSettingsScreen::~GameSettingsScreen() {
Reporting::Enable(enableReports_, "report.ppsspp.org");
Reporting::Enable(systemState_.enableReports, "report.ppsspp.org");
Reporting::UpdateConfig();
if (!g_Config.Save("GameSettingsScreen::onFinish")) {
std::string message = "Failed to save settings!\n";
@@ -170,55 +164,10 @@ void GameSettingsScreen::PreCreateViews() {
ReloadAllPostShaderInfo(screenManager()->getDrawContext());
ReloadAllThemeInfo();
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
CheckMemstickInstallState();
CheckMemstickInstallState(&systemState_);
#endif
}
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
// Works out where the memstick is (installed.txt next to the exe means My Documents or a custom
// folder), and which of the two checkboxes can be changed.
void GameSettingsScreen::CheckMemstickInstallState() {
const Path &PPSSPPpath = File::GetExeDirectory();
const Path installedFile = PPSSPPpath / "installed.txt";
const bool myDocsExists = W32Util::UserDocumentsPath().size() != 0;
installed_ = File::Exists(installedFile);
otherinstalled_ = false;
memstickMyDocEnabled_ = !PSP_IsInited();
memstickOtherEnabled_ = false;
if (!installed_ && myDocsExists) {
// Disable the setting if we can't create & delete a file next to the exe.
if (File::CreateEmptyFile(PPSSPPpath / "installedTEMP.txt")) {
if (!(File::Delete(PPSSPPpath / "installedTEMP.txt")))
memstickMyDocEnabled_ = false;
else
memstickOtherEnabled_ = !PSP_IsInited();
} else
memstickMyDocEnabled_ = false;
} else {
if (installed_ && myDocsExists) {
FILE *testInstalled = File::OpenCFile(installedFile, "rt");
if (testInstalled) {
char temp[2048];
char *tempStr = fgets(temp, sizeof(temp), testInstalled);
// Skip UTF-8 encoding bytes if there are any. There are 3 of them.
if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {
tempStr += 3;
}
memstickOtherEnabled_ = !PSP_IsInited();
if (tempStr && strlen(tempStr) != 0 && strcmp(tempStr, "\n") != 0) {
installed_ = false;
otherinstalled_ = true;
}
fclose(testInstalled);
}
} else if (!myDocsExists) {
memstickMyDocEnabled_ = false;
}
}
}
#endif
const std::vector<GameSettingsScreen::PageDef> &GameSettingsScreen::PageDefs() {
static const std::vector<PageDef> pages = {
{ "GameSettingsGraphics", "Graphics", "I_DISPLAY", "graphics", I18NCat::GRAPHICS, &BuildGraphicsSettings },
@@ -246,429 +195,13 @@ void GameSettingsScreen::CreateTabs() {
std::function<void(SettingsPage *)> build = def.build;
if (!build) {
build = [this](SettingsPage *page) {
BuildSystemSettings(page);
BuildSystemSettings(page, &systemState_);
};
}
AddSettingsTab(def.tag, ms->T(def.title), def.icon ? ImageID(def.icon) : ImageID::invalid(), def.cat, build, def.helpTag);
}
}
void GameSettingsScreen::BuildSystemSettings(SettingsPage *page) {
using namespace UI;
page->Header("UI");
page->Custom("Language", [this](UI::ViewGroup *parent) -> UI::View * {
auto langCodeToName = [](std::string_view value) -> std::string {
auto &mapping = GetLangValuesMapping();
auto iter = mapping.find(value);
if (iter != mapping.end()) {
return iter->second.first;
}
return std::string(value);
};
Choice *language = parent->Add(new ChoiceWithValueDisplay(&g_Config.sLanguageIni, T(I18NCat::SYSTEM, "Language"), langCodeToName));
language->OnClick.Add([this](UI::EventParams &e) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
auto langScreen = new NewLanguageScreen(sy->T("Language"));
// The actual switching is handled in OnCompleted in NewLanguageScreen.
if (e.v)
langScreen->SetPopupOrigin(e.v);
screenManager()->push(langScreen);
});
return language;
});
#if PPSSPP_PLATFORM(IOS)
static const char *indicator[] = {
"Swipe once to switch app (indicator auto-hides)",
"Swipe twice to switch app (indicator stays visible)"
};
page->Setting(&g_Config.iAppSwitchMode);
{
// Note: On iPhone, iOS hides the status bar in landscape no matter what this is set to.
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
page->Setting(&config.bImmersiveMode).OnChange([](EventParams &e) {
System_Notify(SystemNotification::IMMERSIVE_MODE_CHANGE);
});
}
#endif
#if PPSSPP_PLATFORM(ANDROID)
// Hide Immersive Mode on pre-kitkat Android
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 19) {
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
page->Setting(&config.bImmersiveMode).OnChange([this](EventParams &e) {
OnImmersiveModeChange(e);
});
}
#endif
UIContext *ctx = screenManager()->getUIContext();
page->Setting(&g_Config.iUIScaleFactor).OnChange([ctx](UI::EventParams &e) {
const float dpiMul = UIScaleFactorToMultiplier(g_Config.iUIScaleFactor);
g_display.Recalculate(-1, -1, -1, -1, dpiMul);
ctx->InvalidateAtlas();
NativeResized();
});
const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
page->Choice("Clear UI background").OnClick([this](UI::EventParams &e) {
OnChangeBackground(e);
});
} else if (System_GetPropertyBool(SYSPROP_HAS_IMAGE_BROWSER) || System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
page->Choice("Set UI background...").OnClick([this](UI::EventParams &e) {
OnChangeBackground(e);
});
}
page->Setting(&g_Config.bTransparentBackground);
// Shared with achievements.
page->Setting(&g_Config.iNotificationPos);
page->Setting(&g_Config.iBackgroundAnimation);
page->Custom("Theme", [this](UI::ViewGroup *parent) -> UI::View * {
PopupMultiChoiceDynamic *theme = parent->Add(new PopupMultiChoiceDynamic(&g_Config.sThemeName, T(I18NCat::SYSTEM, "Theme"), GetThemeInfoNames(), I18NCat::THEMES, screenManager()));
theme->OnChoice.Add([](EventParams &e) {
UpdateTheme();
// Reset the tint/saturation if the theme changed.
if (e.b) {
g_Config.fUITint = 0.0f;
g_Config.fUISaturation = 1.0f;
}
});
return theme;
});
Draw::DrawContext *draw = screenManager()->getDrawContext();
if (!draw->GetBugs().Has(Draw::Bugs::RASPBERRY_SHADER_COMP_HANG)) {
// We use shaders without tint capability on hardware with this driver bug.
auto noDropShadow = [](View *view) {
static_cast<PopupSliderChoiceFloat *>(view)->SetHasDropShadow(false);
};
page->Setting(&g_Config.fUITint).LiveUpdate().Setup(noDropShadow);
page->Setting(&g_Config.fUISaturation).LiveUpdate().Setup(noDropShadow);
}
page->Header("PSP Memory Stick");
if (System_GetPropertyBool(SYSPROP_HAS_OPEN_DIRECTORY)) {
page->Choice("Show Memory Stick folder").OnClick([](UI::EventParams &p) {
System_LaunchUrl(LaunchUrlType::LOCAL_FOLDER, g_Config.memStickDirectory.ToString());
});
}
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
bool showItHere = true;
#if PPSSPP_PLATFORM(IOS_APP_STORE)
if (g_Config.memStickDirectory == DarwinFileSystemServices::defaultMemoryStickPath()) {
// We still keep a way to access it on the developer tools screen.
showItHere = false;
}
#endif
if (showItHere) {
page->Choice("Set Memory Stick folder").OnClick([this](UI::EventParams &) {
SetMemStickDirDarwin(GetRequesterToken());
});
}
#endif
auto addMemstickPath = [this, &page]() {
page->Custom("Memory Stick folder", [this](UI::ViewGroup *parent) -> UI::View * {
Choice *memstickPath = parent->Add(new ChoiceWithCallbackValueDisplay(T(I18NCat::SYSTEM, "Memory Stick folder"), []() {
return g_Config.memStickDirectory.ToVisualString();
}));
memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnShowMemstickScreen);
return memstickPath;
}).EnabledFunc([] {
return !PSP_IsInited();
});
};
#if PPSSPP_PLATFORM(ANDROID)
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_VR) {
addMemstickPath();
}
#elif defined(_WIN32)
#if PPSSPP_PLATFORM(UWP)
addMemstickPath();
#else
page->Bool(&installed_, "Memory Stick in My Documents").Enabled(&memstickMyDocEnabled_).OnChange([this](UI::EventParams &e) {
OnMemoryStickMyDoc(e);
});
page->Bool(&otherinstalled_, "Memory Stick in installed.txt").Enabled(&memstickOtherEnabled_).OnChange([this](UI::EventParams &e) {
OnMemoryStickOther(e);
});
if (!System_GetPropertyBool(SYSPROP_CAN_GET_FREE_SPACE_FAST)) {
// It might be slow, so use an option.
page->Setting(&g_Config.bReportAccurateFreeStorageSpace);
}
#endif
#endif
page->Setting(&g_Config.bMemStickInserted);
page->Setting(&g_Config.iMemStickSizeGB);
page->Header("Help the PPSSPP team");
page->Bool(&enableReports_, "Enable Compatibility Server Reports").EnabledFunc([]() {
return Reporting::IsSupported();
});
page->Header("Emulation");
page->Setting(&g_Config.bFastMemory).OnChange([this](UI::EventParams &e) {
OnJitAffectingSetting(e);
});
page->Setting(&g_Config.bIgnoreBadMemAccess);
page->Setting(&g_Config.iIOTimingMethod);
page->Setting(&g_Config.bForceLagSync).Disabled(&g_Config.bAutoFrameSkip);
page->Setting(&g_Config.iLockedCPUSpeed);
page->Header({ I18NCat::SAVEDATA, "Save states" }); // Borrow this string from the savedata manager
page->Setting(&g_Config.bEnableStateUndo);
page->Setting(&g_Config.bConfirmLoadState);
page->Setting(&g_Config.iSaveStateSlotCount);
// NOTE: We will soon support more states, but we'll keep this niche feature limited to the first five.
page->Setting(&g_Config.iAutoLoadSaveState).Setup([](View *view) {
if (g_Config.iAutoLoadSaveState != 1) {
static_cast<PopupMultiChoice *>(view)->HideChoice(1); // Hide "Oldest Save" if not using that mode. It doesn't make sense.
}
});
page->Setting(&g_Config.iRewindSnapshotInterval);
page->Header("General");
page->Setting(&g_Config.iAskForExitConfirmationAfterSeconds);
if (System_GetPropertyBool(SYSPROP_CAN_RESTRICT_ORIENTATION)) {
// Display rotation
page->Custom({ I18NCat::CONTROLS, "Screen Rotation" }, [this](UI::ViewGroup *parent) -> UI::View * {
AddRotationPicker(screenManager(), parent, true);
return parent->GetViewByIndex(parent->GetNumSubviews() - 1);
});
}
if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
page->Setting(&g_Config.bSustainedPerformanceMode).OnChange([this](UI::EventParams &e) {
OnSustainedPerformanceModeChange(e);
});
}
page->Choice("Restore Default Settings").OnClick([this](UI::EventParams &e) {
OnRestoreDefaultSettings(e);
});
if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD))
page->Setting(&g_Config.bBypassOSKWithKeyboard);
if (System_GetPropertyBool(SYSPROP_ENOUGH_RAM_FOR_FULL_ISO)) {
page->Setting(&g_Config.bCacheFullIsoInRam).EnabledFunc([] {
return !PSP_IsInited();
});
}
page->Setting(&g_Config.bCheckForNewVersion);
page->Setting(&g_Config.bScreenshotsAsPNG);
page->Setting(&g_Config.iScreenshotMode);
// TODO: Make this setting available on Mac too.
#if PPSSPP_PLATFORM(WINDOWS) || (defined (SDL) && !defined(MOBILE_DEVICE))
page->Setting(&g_Config.bPauseOnLostFocus);
#endif
std::vector<std::string> cameraList = Camera::getDeviceList();
if (cameraList.size() >= 1) {
page->Header({ I18NCat::GRAPHICS, "Camera" });
page->Setting(&g_Config.sCameraDevice, std::move(cameraList));
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
page->Setting(&g_Config.bCameraMirrorHorizontal);
#endif
}
page->Header("Cheats");
page->Setting(&g_Config.bEnableCheats);
page->Setting(&g_Config.bEnablePlugins);
page->Header("PSP Settings");
// The ordering here is simply mapping directly to PSP_SYSTEMPARAM_LANGUAGE_*.
page->Setting(&g_Config.iLanguage);
page->Setting(&g_Config.iPSPModel).EnabledFunc([] {
return !PSP_IsInited();
});
page->Custom("Nickname", [this](UI::ViewGroup *parent) {
return AddNicknameChoice(this, parent);
});
page->Setting(&g_Config.bDayLightSavings);
page->Setting(&g_Config.iDateFormat);
page->Setting(&g_Config.iTimeFormat);
page->Setting(&g_Config.iButtonPreference);
#if defined(_WIN32)
page->Header("Recording");
page->Setting(&g_Config.bDumpFrames);
page->Setting(&g_Config.bUseFFV1);
page->Setting(&g_Config.bDumpVideoOutput);
page->Setting(&g_Config.bDumpAudio);
page->Setting(&g_Config.bSaveLoadResetsAVdumping);
#endif
}
void GameSettingsScreen::OnImmersiveModeChange(UI::EventParams &e) {
System_Notify(SystemNotification::IMMERSIVE_MODE_CHANGE);
if (g_Config.iAndroidHwScale != 0) {
System_RecreateActivity();
}
}
void GameSettingsScreen::OnSustainedPerformanceModeChange(UI::EventParams &e) {
System_Notify(SystemNotification::SUSTAINED_PERF_CHANGE);
}
void GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) {
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
}
void GameSettingsScreen::OnShowMemstickScreen(UI::EventParams &e) {
screenManager()->push(new MemStickScreen(false));
}
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
void GameSettingsScreen::OnMemoryStickMyDoc(UI::EventParams &e) {
const Path &PPSSPPpath = File::GetExeDirectory();
const Path installedFile = PPSSPPpath / "installed.txt";
installed_ = File::Exists(installedFile);
if (otherinstalled_) {
File::Delete(PPSSPPpath / "installed.txt");
File::CreateEmptyFile(PPSSPPpath / "installed.txt");
otherinstalled_ = false;
const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/";
g_Config.memStickDirectory = Path(myDocsPath);
} else if (installed_) {
File::Delete(PPSSPPpath / "installed.txt");
installed_ = false;
g_Config.memStickDirectory = PPSSPPpath / "memstick";
} else {
FILE *f = File::OpenCFile(PPSSPPpath / "installed.txt", "wb");
if (f) {
fclose(f);
}
const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/";
g_Config.memStickDirectory = Path(myDocsPath);
installed_ = true;
}
}
void GameSettingsScreen::OnMemoryStickOther(UI::EventParams &e) {
const Path &PPSSPPpath = File::GetExeDirectory();
if (otherinstalled_) {
auto di = GetI18NCategory(I18NCat::DIALOG);
std::string initialPath = g_Config.memStickDirectory.ToCString();
std::string folder = W32Util::BrowseForFolder2(MainWindow::GetHWND(), di->T("Choose PPSSPP save folder"), initialPath);
if (folder.size()) {
g_Config.memStickDirectory = Path(folder);
FILE *f = File::OpenCFile(PPSSPPpath / "installed.txt", "wb");
if (f) {
std::string utfstring("\xEF\xBB\xBF");
utfstring.append(folder);
fwrite(utfstring.c_str(), 1, utfstring.length(), f);
fclose(f);
}
installed_ = false;
}
else
otherinstalled_ = false;
}
else {
File::Delete(PPSSPPpath / "installed.txt");
memstickMyDocEnabled_ = true;
otherinstalled_ = false;
installed_ = false;
g_Config.memStickDirectory = PPSSPPpath / "memstick";
}
}
#endif
void GameSettingsScreen::OnChangeBackground(UI::EventParams &e) {
const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
INFO_LOG(Log::UI, "Clearing background image.");
// The button is in clear mode.
File::Delete(bgPng);
File::Delete(bgJpg);
UIBackgroundShutdown();
RecreateViews();
return;
}
auto sy = GetI18NCategory(I18NCat::SYSTEM);
System_BrowseForImage(GetRequesterToken(), sy->T("Set UI background..."), bgJpg, [this](std::string_view value, int converted) {
if (converted == 1) {
// The platform code converted and saved the file to the desired path already.
INFO_LOG(Log::UI, "Platform converted the file: %.*s", STR_VIEW(value));
} else if (!value.empty()) {
Path path(value);
// Check the file format. Don't rely on the file extension here due to scoped storage URLs.
FILE *f = File::OpenCFile(path, "rb");
uint8_t buffer[8];
ImageFileType type = ImageFileType::UNKNOWN;
if (f != nullptr && 8 == fread(buffer, 1, ARRAY_SIZE(buffer), f)) {
type = DetectImageFileType(buffer, ARRAY_SIZE(buffer));
}
if (f) {
fclose(f);
}
std::string filename;
switch (type) {
case ImageFileType::JPEG:
filename = "background.jpg";
break;
case ImageFileType::PNG:
filename = "background.png";
break;
default:
break;
}
if (!filename.empty()) {
Path dest = GetSysDirectory(DIRECTORY_SYSTEM) / filename;
File::Copy(path, dest);
if (path.FilePathContainsNoCase("temp_import.jpg")) {
INFO_LOG(Log::UI, "Deleting temp file: %s", GetFriendlyPath(path).c_str());
File::Delete(path);
}
} else {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Only JPG and PNG images are supported"), path.GetFilename(), 5.0);
}
}
// It will init again automatically. We can't init outside a frame on Vulkan.
UIBackgroundShutdown();
RecreateViews();
});
// Change to a browse or clear button.
}
void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult result) {
if (equals(dialog->tag(), "NewLanguage") && result == DR_OK) {
screenManager()->RecreateAllViews();
@@ -686,7 +219,7 @@ void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult resul
if (result == DialogResult::DR_OK) {
if (equals(dialog->tag(), "RestoreSettingsScreen")) {
ReadConfigTemporaries();
ReadSystemPageState(&systemState_);
}
recreate = true;
}
@@ -738,29 +271,6 @@ void GameSettingsScreen::RestartOrDo(std::function<void()> callback) {
}));
}
void GameSettingsScreen::CallbackRestoreDefaults(bool yes) {
if (yes) {
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS);
ReadConfigTemporaries();
}
System_Notify(SystemNotification::UI);
}
void GameSettingsScreen::OnRestoreDefaultSettings(UI::EventParams &e) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
if (g_Config.IsGameSpecific()) {
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
auto di = GetI18NCategory(I18NCat::DIALOG);
auto sy = GetI18NCategory(I18NCat::SYSTEM);
screenManager()->push(
new UI::MessagePopupScreen(sy->T("Restore Default Settings"), dev->T("RestoreGameDefaultSettings", "Are you sure you want to restore the game-specific settings back to the ppsspp defaults?\n"),
di->T("OK"), di->T("Cancel"), [this](bool yes) { CallbackRestoreDefaults(yes); }));
} else {
std::string_view title = sy->T("Restore Default Settings");
screenManager()->push(new RestoreSettingsScreen(title));
}
}
void GestureMappingScreen::CreateTabs() {
auto di = GetI18NCategory(I18NCat::DIALOG);
AddTab("Gesture", di->T("Left side"), [this](UI::LinearLayout *parent) { CreateGestureTab(parent, 0, GetDeviceOrientation() == DeviceOrientation::Portrait); });