From a5b470b2c35161ede0c776e4edd57f86f09c1417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 16:01:34 +0100 Subject: [PATCH 1/2] Settings: Move the settings tabs' builders to a file each in UI/Settings Only moves code. The tabs other than System were built by static functions in GameSettingsScreen.cpp, in place of the old methods, to keep the previous change's diff readable. Now each tab has its own file, with just the includes it needs, and MainSettingsPages.h declares their builders. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/CMakeLists.txt | 7 + UI/GameSettingsScreen.cpp | 906 +----------------------------- UI/Settings/AudioPage.cpp | 182 ++++++ UI/Settings/ControlsPage.cpp | 161 ++++++ UI/Settings/GraphicsPage.cpp | 381 +++++++++++++ UI/Settings/MainSettingsPages.h | 37 ++ UI/Settings/NetworkingPage.cpp | 270 +++++++++ UI/Settings/ToolsPage.cpp | 60 ++ UI/Settings/VRPage.cpp | 50 ++ UI/UI.vcxproj | 7 + UI/UI.vcxproj.filters | 24 + UWP/UI_UWP/UI_UWP.vcxproj | 7 + UWP/UI_UWP/UI_UWP.vcxproj.filters | 24 + android/jni/Android.mk | 6 + 14 files changed, 1217 insertions(+), 905 deletions(-) create mode 100644 UI/Settings/AudioPage.cpp create mode 100644 UI/Settings/ControlsPage.cpp create mode 100644 UI/Settings/GraphicsPage.cpp create mode 100644 UI/Settings/MainSettingsPages.h create mode 100644 UI/Settings/NetworkingPage.cpp create mode 100644 UI/Settings/ToolsPage.cpp create mode 100644 UI/Settings/VRPage.cpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index a1041fd2d1..583f1d9ab9 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -63,6 +63,13 @@ list(APPEND UISource TabbedDialogScreen.cpp SettingsPage.h SettingsPage.cpp + Settings/MainSettingsPages.h + Settings/GraphicsPage.cpp + Settings/ControlsPage.cpp + Settings/AudioPage.cpp + Settings/NetworkingPage.cpp + Settings/ToolsPage.cpp + Settings/VRPage.cpp SettingsPagesDialogScreen.h SettingsPagesDialogScreen.cpp GameScreen.h diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index a032f247c7..5b10483104 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -44,6 +44,7 @@ #include "Common/UI/PopupScreens.h" #include "UI/EmuScreen.h" #include "UI/GameSettingsScreen.h" +#include "UI/Settings/MainSettingsPages.h" #include "UI/SettingsPage.h" #include "UI/GameInfoCache.h" #include "UI/GamepadEmu.h" @@ -173,32 +174,6 @@ void GameSettingsScreen::PreCreateViews() { #endif } -static bool UsingHardwareTextureScaling() { - return g_Config.bTexHardwareScaling && (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::METAL) && !g_Config.bSoftwareRendering; -} - -static std::string TextureTranslateName(std::string_view value) { - const TextureShaderInfo *info = GetTextureShaderInfo(value); - if (info) { - auto ts = GetI18NCategory(I18NCat::TEXTURESHADERS); - return std::string(ts->T(value, info->name.c_str())); - } else { - return std::string(value); - } -} - -static std::string *GPUDeviceNameSetting() { - if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { - return &g_Config.sVulkanDevice; - } -#ifdef _WIN32 - if (g_Config.iGPUBackend == (int)GPUBackend::DIRECT3D11) { - return &g_Config.sD3D11Device; - } -#endif - return nullptr; -} - #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. @@ -244,14 +219,6 @@ void GameSettingsScreen::CheckMemstickInstallState() { } #endif -// The tabs other than System, which needs more of the screen. -static void BuildGraphicsSettings(SettingsPage *page); -static void BuildControlsSettings(SettingsPage *page); -static void BuildAudioSettings(SettingsPage *page); -static void BuildNetworkingSettings(SettingsPage *page); -static void BuildToolsSettings(SettingsPage *page); -static void BuildVRSettings(SettingsPage *page); - const std::vector &GameSettingsScreen::PageDefs() { static const std::vector pages = { { "GameSettingsGraphics", "Graphics", "I_DISPLAY", "graphics", I18NCat::GRAPHICS, &BuildGraphicsSettings }, @@ -286,850 +253,6 @@ void GameSettingsScreen::CreateTabs() { } } -// TODO: Make this generic -extern int DefaultDepthRaster(); - -static void OnRenderingBackend(SettingsPageHost *host) { - // It only makes sense to show the restart prompt if the backend was actually changed. - if (g_Config.iGPUBackend != (int)GetGPUBackend()) { - host->RestartOrDo([]() { - g_Config.iGPUBackend = (int)GetGPUBackend(); - }); - } -} - -static void OnRenderingDevice(SettingsPageHost *host) { - // It only makes sense to show the restart prompt if the device was actually changed. - std::string *deviceNameSetting = GPUDeviceNameSetting(); - if (deviceNameSetting && *deviceNameSetting != GetGPUBackendDevice()) { - host->RestartOrDo([host]() { - std::string *deviceNameSetting = GPUDeviceNameSetting(); - if (deviceNameSetting) - *deviceNameSetting = GetGPUBackendDevice(); - // Needed to redraw the setting. - host->RecreatePageViews(); - }); - } -} - -static void BuildGraphicsSettings(SettingsPage *page) { - using namespace UI; - SettingsPageHost *host = page->Host(); - - Draw::DrawContext *draw = host->PageScreenManager()->getDrawContext(); - int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); - - page->Header("Rendering Mode"); - -#if !PPSSPP_PLATFORM(UWP) - page->Setting(&g_Config.iGPUBackend).Setup([host](View *view) { - PopupMultiChoice *renderingBackendChoice = static_cast(view); - renderingBackendChoice->SetPreOpenCallback([host](UI::PopupMultiChoice *choice) { - // Don't filter until the last possible moment, since it involves trying to initialize Vulkan, if we were - // started in OpenGL mode on Android. - choice->HideChoice(1); - choice->OnChoice.Add([host](UI::EventParams &) { - OnRenderingBackend(host); - }); - - if (!g_Config.IsBackendEnabled(GPUBackend::OPENGL)) - choice->HideChoice((int)GPUBackend::OPENGL); - if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D11)) - choice->HideChoice((int)GPUBackend::DIRECT3D11); - if (!g_Config.IsBackendEnabled(GPUBackend::VULKAN)) - choice->HideChoice((int)GPUBackend::VULKAN); - if (!g_Config.IsBackendEnabled(GPUBackend::METAL)) - choice->HideChoice((int)GPUBackend::METAL); - }); - - if (!IsFirstInstance()) { - // If we're not the first instance, can't save the setting, and it requires a restart, so... - renderingBackendChoice->SetEnabled(false); - } - }); -#endif - -#if (PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(TVOS)) || PPSSPP_PLATFORM(ANDROID) - // Showing the game there needs a second swapchain. Otherwise the system mirrors the screen. - page->Setting(&g_Config.iExternalDisplay).EnabledFunc([draw] { - return draw->GetDeviceCaps().multipleSwapchains; - }).OnChange([previous = g_Config.iExternalDisplay](UI::EventParams &e) mutable { - // Picking the current value again would rebuild the TV's output for nothing (iOS starts over on a change). - if (g_Config.iExternalDisplay == previous) { - return; - } - previous = g_Config.iExternalDisplay; - System_Notify(SystemNotification::EXTERNAL_DISPLAY_CHANGED); - }); -#endif - - // Backends that don't allow a device choice will only expose one device. - if (draw->GetDeviceList().size() > 1) { - std::string *deviceNameSetting = GPUDeviceNameSetting(); - if (deviceNameSetting) { - page->Setting(deviceNameSetting, draw->GetDeviceList()).OnChange([host](UI::EventParams &e) { - OnRenderingDevice(host); - }); - } - } - - page->Setting(&g_Config.iInternalResolution).EnabledFunc([] { - return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects; - }); - - if (deviceType != DEVICE_TYPE_VR) { - page->Setting(&g_Config.bSoftwareRendering).EnabledFunc([] { - return !PSP_IsInited(); - }); - } - - if (draw->GetDeviceCaps().multiSampleLevelsMask != 1) { - page->Setting(&g_Config.iMultiSampleLevel).EnabledFunc([] { - return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects; - }).Setup([draw](View *view) { - PopupMultiChoice *msaaChoice = static_cast(view); - if (g_Config.iMultiSampleLevel > 1 && draw->GetDeviceCaps().isTilingGPU) { - msaaChoice->SetIconRight(ImageID("I_WARNING"), 0.7f); - } - // Hide unsupported levels. - for (int i = 1; i < msaaChoice->NumChoices(); i++) { - if ((draw->GetDeviceCaps().multiSampleLevelsMask & (1 << i)) == 0) { - msaaChoice->HideChoice(i); - } else if (i > 0 && draw->GetDeviceCaps().isTilingGPU) { - msaaChoice->SetChoiceIcon(i, ImageID("I_WARNING")); - } - } - }); - } - -#if PPSSPP_PLATFORM(ANDROID) - if ((deviceType != DEVICE_TYPE_TV) && (deviceType != DEVICE_TYPE_VR)) { - int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 2; - if (max_res == 3) - max_res = 4; // At least allow 2x - page->Setting(&g_Config.iAndroidHwScale).Setup([max_res](View *view) { - // Hide the scales the display is too small for. - PopupMultiChoice *choice = static_cast(view); - for (int i = max_res; i < choice->NumChoices(); i++) { - choice->HideChoice(i); - } - }); - } -#endif - - DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); - - page->Setting(&g_Config.bReplaceTextures); - - page->Header("Display"); - - if (deviceType != DEVICE_TYPE_VR) { -#if !defined(MOBILE_DEVICE) - page->Setting(&g_Config.bFullScreen); - if (draw->GetDeviceCaps().fullScreenExclusiveSupported) { - page->Setting(&g_Config.bAllowFullScreenExclusive).EnabledFunc([] { - return g_Config.bFullScreen && !g_Config.bFullScreenMulti; - }).OnChange([host](UI::EventParams &e) { - host->RestartOrDo([host]() { - g_Config.bAllowFullScreenExclusive = !g_Config.bAllowFullScreenExclusive; - host->RecreatePageViews(); - }); - }); - } - if (System_GetPropertyInt(SYSPROP_DISPLAY_COUNT) > 1) { - page->Setting(&g_Config.bFullScreenMulti).EnabledFunc([] { - return g_Config.bFullScreen && !g_Config.bAllowFullScreenExclusive; - }); - } -#endif - // Display Layout Editor: To avoid overlapping touch controls on large tablets, meet geeky demands for integer zoom/unstretched image etc. - page->Choice("Display layout & effects").OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new DisplayLayoutScreen(host->PageGamePath())); - }); - } - - // If only one mode is supported (like FIFO on iOS), no need to show the options. - if (CountSetBits((u32)draw->GetDeviceCaps().presentModesSupported) > 1) { - // Immediate means non-synchronized, tearing. - if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE) { - page->Setting(&g_Config.bVSync).OnChange([](UI::EventParams &e) { - NativeResized(); // TODO: Remove - }); - } - if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::MAILBOX) { - SettingItem &lowLatency = page->Setting(&g_Config.bLowLatencyPresent).OnChange([](UI::EventParams &e) { - NativeResized(); // TODO: Remove - }); - - // If the immediate mode is supported, we can tie low latency present to VSync. - if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE) { - lowLatency.Enabled(&g_Config.bVSync); - } - } - } - - page->Header("Frame Rate Control"); - - page->Setting(&g_Config.iAltSpeedPercent1); - page->Setting(&g_Config.iAltSpeedPercent2); - if (KeyMap::InputMappingsFromPspButton(VIRTKEY_SPEED_ANALOG, nullptr, true)) { - page->Setting(&g_Config.iAnalogAltSpeedPercent); - } - - page->Header({ I18NCat::GRAPHICS, "Speed Hacks", "Speed Hacks (can cause rendering errors!)" }); - - page->Setting(&g_Config.iFrameSkip).EnabledFunc([] { - return !g_Config.bAutoFrameSkip; - }).Setup([](View *view) { - PopupMultiChoice *choice = static_cast(view); - choice->SetChoicesUntranslated(1, choice->NumChoices() - 1); - }); - - page->Setting(&g_Config.bAutoFrameSkip); - - page->Setting(&g_Config.bSkipBufferEffects).Disabled(&g_Config.bSoftwareRendering); - page->Hint({ I18NCat::GRAPHICS, "RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games" }); - - page->Setting(&g_Config.iSkipGPUReadbackMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) { - static_cast(view)->SetDefault(0); - }); - - page->Setting(&g_Config.iDepthRasterMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) { - PopupMultiChoice *depthRasterMode = static_cast(view); - depthRasterMode->SetChoiceIcon(3, ImageID("I_WARNING")); // It's a performance trap. - depthRasterMode->SetDefault(DefaultDepthRaster()); - if (g_Config.iDepthRasterMode != 3) - depthRasterMode->HideChoice(3); - }); - - page->Setting(&g_Config.iBloomHack).EnabledFunc([] { - return !g_Config.bSoftwareRendering && g_Config.iInternalResolution != 1; - }); - page->Hint("Reduces artifacts"); - - page->Header("Performance"); - page->Setting(&g_Config.bRenderDuplicateFrames).EnabledFunc([] { - return !g_Config.bSkipBufferEffects && g_Config.iFrameSkip == 0; - }); - page->Hint({ I18NCat::GRAPHICS, "RenderDuplicateFrames Tip", "Can make framerate smoother in games that run at lower framerates" }); - - if (draw->GetDeviceCaps().setMaxFrameLatencySupported) { - // Changing it needs a restart, so the value in use is the one the page was built with. - page->Setting(&g_Config.iInflightFrames).OnChange([host, previous = g_Config.iInflightFrames](UI::EventParams &e) { - if (g_Config.iInflightFrames != previous) { - host->RestartOrDo([previous]() { - g_Config.iInflightFrames = previous; - }); - } - }); - page->Hint("Faster, input lag"); // TODO: This hint could use improvement. - } - - page->Header("Texture upscaling"); - - if (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::METAL) { - page->Custom("GPU texture upscaler (fast)", [host](UI::ViewGroup *parent) -> UI::View * { - auto gr = GetI18NCategory(I18NCat::GRAPHICS); - ChoiceWithValueDisplay *textureShaderChoice = parent->Add(new ChoiceWithValueDisplay(&g_Config.sTextureShaderName, gr->T("GPU texture upscaler (fast)"), &TextureTranslateName)); - textureShaderChoice->OnClick.Add([host](UI::EventParams &e) { - auto gr = GetI18NCategory(I18NCat::GRAPHICS); - auto shaderScreen = new TextureShaderScreen(gr->T("GPU texture upscaler (fast)")); - shaderScreen->OnChoice.Add([host](UI::EventParams &e) { - System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); - host->RecreatePageViews(); // Update setting name - g_Config.bTexHardwareScaling = g_Config.sTextureShaderName != "Off"; - }); - if (e.v) - shaderScreen->SetPopupOrigin(e.v); - host->PageScreenManager()->push(shaderScreen); - }); - return textureShaderChoice; - }).Disabled(&g_Config.bSoftwareRendering); - } - - page->Setting(&g_Config.iTexScalingType).EnabledFunc([]() { - return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); - }); - page->Setting(&g_Config.iTexScalingLevel).EnabledFunc([]() { - return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); - }); - page->Hint({ I18NCat::GRAPHICS, "UpscaleLevel Tip", "CPU heavy - some scaling may be delayed to avoid stutter" }); - - page->Setting(&g_Config.bTexDeposterize).EnabledFunc([]() { - return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); - }); - page->Hint({ I18NCat::GRAPHICS, "Deposterize Tip", "Fixes visual banding glitches in upscaled textures" }); - - page->Header("Texture Filtering"); - - page->Setting(&g_Config.iAnisotropyLevel).Disabled(&g_Config.bSoftwareRendering); - - page->Setting(&g_Config.iTexFiltering).Disabled(&g_Config.bSoftwareRendering); - - page->Setting(&g_Config.bSmart2DTexFiltering).Disabled(&g_Config.bSoftwareRendering); - -#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) - bool showCardboardSettings = deviceType != DEVICE_TYPE_VR; -#else - // If you enabled it through the ini, you can see this. Useful for testing. - bool showCardboardSettings = config.bEnableCardboardVR; -#endif - if (showCardboardSettings) { - page->Header("Cardboard VR Settings"); - page->Setting(&config.bEnableCardboardVR); - page->Setting(&config.iCardboardScreenSize).Enabled(&config.bEnableCardboardVR); - page->Setting(&config.iCardboardXShift).Enabled(&config.bEnableCardboardVR); - page->Setting(&config.iCardboardYShift).Enabled(&config.bEnableCardboardVR); - } - - page->Header("Overlay Information"); - auto addStatusFlag = [page](ShowStatusFlags flag, const char *text) { - page->Custom(text, [flag, text](UI::ViewGroup *parent) -> UI::View * { - return parent->Add(new BitCheckBox(&g_Config.iShowStatusFlags, (int)flag, T(I18NCat::GRAPHICS, text))); - }); - }; - addStatusFlag(ShowStatusFlags::FPS_COUNTER, "Show FPS Counter"); - addStatusFlag(ShowStatusFlags::SPEED_COUNTER, "Show Speed"); - if (System_GetPropertyBool(SYSPROP_CAN_READ_BATTERY_PERCENTAGE)) { - addStatusFlag(ShowStatusFlags::BATTERY_PERCENT, "Show Battery %"); - } - page->Custom({ I18NCat::DEVELOPER, "Debug overlay" }, [host](UI::ViewGroup *parent) -> UI::View * { - AddOverlayList(parent, host->PageScreenManager()); - return parent->GetViewByIndex(parent->GetNumSubviews() - 1); - }); -} - -static void BuildAudioSettings(SettingsPage *page) { - using namespace UI; - -#if PPSSPP_PLATFORM(IOS) - page->Setting(&g_Config.bAudioRespectSilentMode).Enabled(&g_Config.bEnableSound); -#endif -#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) - page->Setting(&g_Config.bAudioMixWithOthers).Enabled(&g_Config.bEnableSound); -#endif - page->Header("Audio playback"); - - page->Setting(&g_Config.iAudioPlaybackMode); - page->Setting(&g_Config.bFillAudioGaps).EnabledFunc([]() { - return g_Config.iAudioPlaybackMode == (int)AudioSyncMode::GRANULAR; - }); - - page->Header("Game volume"); - - // This is here because it now only applies to in-game. Muting the menu sounds is separate. - page->Setting(&g_Config.bEnableSound); - - page->Setting(&g_Config.iGameVolume).Enabled(&g_Config.bEnableSound); - page->Setting(&g_Config.iReverbVolume).Enabled(&g_Config.bEnableSound); - page->Setting(&g_Config.iAltSpeedVolume).Enabled(&g_Config.bEnableSound); - page->Setting(&g_Config.iAchievementVolume).Enabled(&g_Config.bEnableSound).OnChange([](EventParams &e) { - // Audio preview - float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume); - g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume); - }); - - page->Header(); - - page->Setting(&g_Config.bUISound); - page->Setting(&g_Config.iUIVolume).LiveUpdate().Enabled(&g_Config.bUISound).OnChange([](EventParams &e) { - static double lastTimePlayed = 0.0; - double now = time_now_d(); - if (now - lastTimePlayed < 0.1) { - return; // Don't play if we just played one, to avoid spamming when dragging. - } - lastTimePlayed = now; - // Audio preview - PlayUISound(UI::UISound::CONFIRM); - }); - - page->Setting(&g_Config.iGamePreviewVolume); - - bool sdlAudio = false; - -#if defined(SDL) - page->Header("Audio backend"); - std::vector audioDeviceList; - SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList); - audioDeviceList.insert(audioDeviceList.begin(), std::string(T(I18NCat::AUDIO, "Auto"))); - page->Setting(&g_Config.sAudioDevice, std::move(audioDeviceList)).OnChange([](EventParams &e) { - if (g_Config.sAudioDevice == T(I18NCat::AUDIO, "Auto")) { - g_Config.sAudioDevice.clear(); - } - System_Notify(SystemNotification::AUDIO_RESET_DEVICE); - }); - sdlAudio = true; - - static const int bufferSizes[] = {128, 256, 512, 1024, 2048}; - page->IntSlider(&g_Config.iSDLAudioBufferSize, 0, 2048, "Buffer size").Default(256).RestrictTo(bufferSizes, ARRAY_SIZE(bufferSizes)); - page->Hint({ I18NCat::DIALOG, "This change will not take effect until PPSSPP is restarted." }); -#endif - -#if PPSSPP_PLATFORM(WINDOWS) - extern AudioBackend *g_audioBackend; - - std::vector audioDeviceNames; - std::vector audioDeviceIds; - - std::vector deviceDescs; - g_audioBackend->EnumerateDevices(&deviceDescs); - if (!deviceDescs.empty()) { - page->Header("Audio backend"); - for (auto &desc : deviceDescs) { - audioDeviceNames.push_back(desc.name); - audioDeviceIds.push_back(desc.uniqueId); - } - - audioDeviceNames.insert(audioDeviceNames.begin(), std::string(T(I18NCat::AUDIO, "Auto"))); - audioDeviceIds.insert(audioDeviceIds.begin(), ""); - - page->Setting(&g_Config.sAudioDevice, std::move(audioDeviceNames), std::move(audioDeviceIds)).OnChange([](EventParams &) { - bool reverted; - if (g_audioBackend->InitOutputDevice(g_Config.sAudioDevice, LatencyMode::Aggressive, &reverted)) { - if (reverted) { - WARN_LOG(Log::Audio, "Unexpected: After a direct choice, audio device reverted to default. '%s'", g_Config.sAudioDevice.c_str()); - } - } else { - WARN_LOG(Log::Audio, "InitOutputDevice failed"); - } - }); - page->Setting(&g_Config.bAutoSwitchAudioDevice).EnabledFunc([]()->bool { - return g_Config.sAudioDevice.empty(); - }); - } - -#else - if (sdlAudio) { - page->Setting(&g_Config.bAutoSwitchAudioDevice); - } else { - page->Header("Audio backend"); - } - -#if PPSSPP_PLATFORM(ANDROID) - page->Setting(&g_Config.bExtraAudioBuffering); - - auto restartAudio = [](UI::EventParams &) { - AndroidAudio_Restart(g_audioState); - }; - // AAudio isn't stable before Android 8.1, so there it's always OpenSL ES. - if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 27) { - page->Setting(&g_Config.iAndroidAudioApi).OnChange(restartAudio); - page->Hint({ I18NCat::AUDIO, "AudioApiHint", "Try the other one if you have no sound or other audio problems." }); - - page->Setting(&g_Config.bAndroidAudioMMap).OnChange(restartAudio).EnabledFunc([]() { - return g_Config.iAndroidAudioApi == (int)AndroidAudioApi::AAUDIO; - }); - page->Hint({ I18NCat::AUDIO, "AudioMMapHint", "Lowest audio delay. On some devices, it causes silence over Bluetooth or other connected audio devices. Turn it off if you get no sound." }); - } - // Show Oboe debug info - const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState); - if (!audioErrorStr.empty()) { - page->Info("Audio Error", audioErrorStr); - } -#endif -#endif - - std::vector micList = Microphone::getDeviceList(); - if (!micList.empty()) { - page->Header("Microphone"); - std::vector micValues = micList; - micList.emplace(micList.begin(), T(I18NCat::AUDIO, "Auto")); - micValues.insert(micValues.begin(), ""); - page->Setting(&g_Config.sMicDevice, std::move(micList), std::move(micValues)); - } -} - -static void BuildControlsSettings(SettingsPage *page) { - using namespace UI; - SettingsPageHost *host = page->Host(); - - int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); - - page->Header({ I18NCat::MAINSETTINGS, "Controls" }); - page->Choice("Control mapping").OnClick([host](UI::EventParams &e) { - host->PageScreenManager()->push(new ControlMappingScreen(host->PageGamePath())); - }); - page->Choice("Calibrate analog stick").OnClick([host](UI::EventParams &e) { - host->PageScreenManager()->push(new AnalogCalibrationScreen(host->PageGamePath())); - }); - -#if (PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID)) - page->Setting(&g_Config.bSystemControls); -#endif -#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - page->Setting(&g_Config.bGamepadOnlyFocused); -#endif - - if (System_GetPropertyBool(SYSPROP_HAS_ACCELEROMETER)) { - page->Custom("Tilt control setup", [host](UI::ViewGroup *parent) -> UI::View * { - // Show the tilt type on the item. - Choice *customizeTilt = parent->Add(new ChoiceWithCallbackValueDisplay(T(I18NCat::CONTROLS, "Tilt control setup"), []() -> std::string { - if (g_Config.bTiltInputEnabled && (u32)g_Config.iTiltInputType < (u32)g_numTiltTypes) { - auto co = GetI18NCategory(I18NCat::CONTROLS); - return std::string(co->T(g_tiltTypes[g_Config.iTiltInputType])); - } else { - auto di = GetI18NCategory(I18NCat::DIALOG); - return std::string(di->T("Disabled")); - } - })); - customizeTilt->OnClick.Add([host](UI::EventParams &e) { - host->PageScreenManager()->push(new TiltAnalogSettingsScreen(host->PageGamePath())); - }); - return customizeTilt; - }); - } else if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_VR) { // TODO: This seems like a regression - page->Setting(&g_Config.bHapticFeedback); - } - - page->Header("Analog to digital mapping"); - page->Setting(&g_Config.fAnalogTriggerThreshold); - page->Setting(&g_Config.fAnalogStickThreshold); - - // TVs don't have touch control, at least not yet. - if ((deviceType != DEVICE_TYPE_TV) && (deviceType != DEVICE_TYPE_VR)) { - page->Header("On-screen touch controls"); - page->Setting(&g_Config.bShowTouchControls); - page->Choice("Edit touch control layout").OnClick([host](UI::EventParams &e) { - host->PageScreenManager()->push(new TouchControlLayoutScreen(host->PageGamePath())); - }).Enabled(&g_Config.bShowTouchControls); - page->Choice("Gesture mapping").OnClick([host](UI::EventParams &e) { - host->PageScreenManager()->push(new GestureMappingScreen(host->PageGamePath())); - }).Enabled(&g_Config.bShowTouchControls); - - page->Setting(&g_Config.iTouchButtonStyle).Enabled(&g_Config.bShowTouchControls); - - page->Setting(&g_Config.iTouchButtonOpacity).Enabled(&g_Config.bShowTouchControls); - page->Setting(&g_Config.iTouchButtonHideSeconds).Enabled(&g_Config.bShowTouchControls); - - page->Setting(&g_Config.bTouchGliding).Enabled(&g_Config.bShowTouchControls); - - TouchControlConfig &touch = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); - - // Hide stick background, useful when increasing the size - page->Setting(&touch.bHideStickBackground).Enabled(&g_Config.bShowTouchControls); - - // Sticky D-pad. - page->Setting(&g_Config.bStickyTouchDPad).Enabled(&g_Config.bShowTouchControls); - page->Hint("Easier sweeping movements"); - - // Re-centers itself to the touch location on touch-down. - page->Setting(&g_Config.bAutoCenterTouchAnalog).Enabled(&g_Config.bShowTouchControls); - - if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { - page->Setting(&g_Config.bHapticFeedback); - } - - // The pause button is now a regular on-screen button. - - page->Setting(&g_Config.bDisableDpadDiagonals).Enabled(&g_Config.bShowTouchControls); - } - -#if PPSSPP_PLATFORM(WINDOWS) - page->Header("Control input sources"); - page->Setting(&g_Config.bAllowHIDInput); - page->Setting(&g_Config.bAllowXInput); - page->Setting(&g_Config.bAllowDInput); -#endif - - if (deviceType != DEVICE_TYPE_VR) { - page->Header({ I18NCat::CONTROLS, "Keyboard", "Keyboard Control Settings" }); -#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - page->Setting(&g_Config.bIgnoreWindowsKey); -#endif - page->Setting(&g_Config.fAnalogLimiterDeadzone); - page->Hint({ I18NCat::CONTROLS, "AnalogLimiter Tip", "When the analog limiter button is pressed" }); - page->Setting(&g_Config.iRapidFireInterval); -#if PPSSPP_PLATFORM(WINDOWS) || defined(SDL) || PPSSPP_PLATFORM(ANDROID) - bool enableMouseSettings = true; -#if PPSSPP_PLATFORM(ANDROID) - if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) < 12) { - enableMouseSettings = false; - } -#endif -#else - bool enableMouseSettings = false; -#endif - if (enableMouseSettings) { - // The mousewheel button-release setting is independent of actual mouse delta control. - page->Header({ I18NCat::CONTROLS, "Mouse", "Mouse settings" }); - page->Setting(&g_Config.iMouseWheelUpDelayMs); - - page->Setting(&g_Config.bMouseControl); - page->Hint({ I18NCat::CONTROLS, "MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon." }); - -#if !PPSSPP_PLATFORM(ANDROID) - page->Setting(&g_Config.bMouseConfine).Enabled(&g_Config.bMouseControl); -#endif - page->Setting(&g_Config.fMouseSensitivity).Enabled(&g_Config.bMouseControl).LiveUpdate(); - page->Setting(&g_Config.fMouseSmoothing).Enabled(&g_Config.bMouseControl).LiveUpdate(); - } - } -} - -// Compound view just like the audio file choosers -class MacAddressChooser : public UI::LinearLayout { -public: - MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr); -}; - -MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { - using namespace UI; - SetSpacing(5.0f); - if (!layoutParams) { - layoutParams_->width = FILL_PARENT; - layoutParams_->height = ITEM_HEIGHT; - } - auto n = GetI18NCategory(I18NCat::NETWORKING); - - std::string initialValue = *value; - Add(new PopupTextInputChoice(token, value, title, g_Config.sMACAddress, 17, screenManager, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) { - // Validate the chosen address, and restore to initialValue if bad. - if (g_Config.sMACAddress.size() != 17) { - // TODO: Alert the user - *value = initialValue; - } - }); - Add(new Choice(n->T("Randomize"), new LinearLayoutParams(WRAP_CONTENT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - auto di = GetI18NCategory(I18NCat::DIALOG); - - std::string_view confirmMessage = n->T("ChangeMacSaveConfirm", "Generate a new MAC address?"); - std::string_view warningMessage = n->T("ChangeMacSaveWarning", "Some games verify the MAC address when loading savedata, so this may break old saves."); - std::string combined = g_Config.sMACAddress + "\n\n" + std::string(confirmMessage) + "\n\n" + std::string(warningMessage); - - auto confirmScreen = new MessagePopupScreen( - title, - combined, di->T("Yes"), di->T("No"), - [](bool success) { - if (success) { - g_Config.sMACAddress = CreateRandMAC(); - } - }); - screenManager->push(confirmScreen); - }); -} - -// The nickname is shown on both the Networking and System tabs. -static UI::View *AddNicknameChoice(SettingsPageHost *host, UI::ViewGroup *parent) { - using namespace UI; - - auto sy = GetI18NCategory(I18NCat::SYSTEM); - PopupTextInputChoice *nickname = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, host->PageScreenManager())); - if (g_Config.sNickName == "PPSSPP") { - nickname->SetIconRight(ImageID("I_WARNING")); - } - nickname->OnChange.Add([host](UI::EventParams &e) { - // Copy to infrastructure name if valid and not already set. - if (g_Config.sInfrastructureUsername.empty()) { - if (g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 3, 16)) { - g_Config.sInfrastructureUsername = g_Config.sNickName; - } - } - // Updates the warning icon, and the username notice on the Networking tab. - host->RecreatePageViews(); - }); - return nickname; -} - -static void BuildNetworkingSettings(SettingsPage *page) { - using namespace UI; - SettingsPageHost *host = page->Host(); - - page->Header({ I18NCat::MAINSETTINGS, "Networking" }); - - page->Choice("Quick start guide for multiplayer", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - std::string url(n->T("MultiplayerQuickStartURL", "https://www.ppsspp.org/docs/multiplayer/quickstart/")); - System_LaunchUrl(LaunchUrlType::BROWSER_URL, url); - }); - - // Ad hoc play, the built-in server and UPnP all talk to the local network. - auto requestLocalNetwork = [](UI::EventParams &e) { - CheckBox *checkBox = static_cast(e.v); - if (checkBox->Toggled()) { - System_RequestLocalNetworkPermission(); - } - }; - - page->Setting(&g_Config.bEnableWlan).OnChange(requestLocalNetwork); - page->Custom("MAC address", [host](UI::ViewGroup *parent) -> UI::View * { - return parent->Add(new MacAddressChooser(host->PageRequesterToken(), host->PageGamePath(), &g_Config.sMACAddress, T(I18NCat::NETWORKING, "MAC address"), host->PageScreenManager())); - }); - - page->Header("Ad Hoc multiplayer"); - - page->Custom("Ad hoc server address", [host](UI::ViewGroup *parent) -> UI::View * { - auto n = GetI18NCategory(I18NCat::NETWORKING); - auto launchAdhocServerScreen = [host](UI::EventParams &) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - host->PageScreenManager()->push(new AdhocServerScreen(&g_Config.sProAdhocServer, n->T("Ad hoc server address"))); - }; - - if (AdhocServerNameIsCustom()) { - LinearLayout *serverRow = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - serverRow->SetSpacing(0.0f); - serverRow->Add(new ChoiceWithValueDisplay(&g_Config.sProAdhocServer, n->T("Ad hoc server address"), I18NCat::NONE, new LinearLayoutParams(1.0f)))->OnClick.Add(launchAdhocServerScreen); - serverRow->Add(new Choice(ImageID("I_EDIT_TEXT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([host](UI::EventParams &) { - AskToEditCurrentServer(host->PageRequesterToken(), host->PageScreenManager()); - }); - return serverRow; - } - - AdhocServerListEntry entry; - if (!g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &entry)) { - Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(ApplySafeSubstitutions("%1 (%2)", entry.name, entry.host), n->T("Ad hoc server address"))); - choice->OnClick.Add(launchAdhocServerScreen); - return choice; - } - // This case happens when updating from a previous version, as well as clicking on one of the local addresses - Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(g_Config.sProAdhocServer, n->T("Ad hoc server address"))); - choice->OnClick.Add(launchAdhocServerScreen); - return choice; - }); - - AdhocServerListEntry serverEntry; - if (!AdhocServerNameIsCustom() && !g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &serverEntry)) { - page->Custom("Server status: %1", [host, serverEntry](UI::ViewGroup *parent) -> UI::View * { - Choice *status = parent->Add(new Choice(ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Server status: %1"), serverEntry.name))); - status->SetIconLeft(ImageID("I_INFO")); - status->OnClick.Add([host, serverEntry](UI::EventParams &) { - host->PageScreenManager()->push(new AdhocServerInfoScreen(serverEntry)); - }); - return status; - }); - } - - page->Setting(&g_Config.iAdhocServerRelayMode).EnabledFunc([] { - return !PSP_IsInited(); - }); - page->Hint({ I18NCat::NETWORKING, "PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower." }); - - page->Custom({ I18NCat::SYSTEM, "Nickname" }, [host](UI::ViewGroup *parent) { - return AddNicknameChoice(host, parent); - }); - page->Hint("Try to pick a unique nickname for ad hoc play"); - - page->Setting(&g_Config.bEnableAdhocServer).OnChange(requestLocalNetwork); - - page->Setting(&g_Config.bAdhocUseUdp).EnabledFunc([] { - return !PSP_IsInited(); - }); - page->Hint("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."); - - page->Setting(&g_Config.bAdhocRelayModeEnetPdpReliable).EnabledFunc([] { - return !PSP_IsInited() && g_Config.bAdhocUseUdp; - }); - page->Hint("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means."); - - page->Header("Infrastructure"); - if (g_Config.sInfrastructureUsername.empty()) { - page->Custom("To play in Infrastructure Mode, you must enter a username", [](UI::ViewGroup *parent) -> UI::View * { - NoticeView *notice = parent->Add(new NoticeView(NoticeLevel::WARN, T(I18NCat::NETWORKING, "To play in Infrastructure Mode, you must enter a username"), "")); - notice->SetWrapText(true); - return notice; - }); - } - page->Custom({ I18NCat::DIALOG, "Username" }, [host](UI::ViewGroup *parent) -> UI::View * { - PopupTextInputChoice *usernameChoice = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureUsername, T(I18NCat::DIALOG, "Username"), "", 16, host->PageScreenManager())); - usernameChoice->SetRestriction(StringRestriction::AlphaNumDashUnderscore, 3); - usernameChoice->OnChange.Add([host](UI::EventParams &e) { - host->RecreatePageViews(); - }); - return usernameChoice; - }); - - page->Setting(&g_Config.bInfrastructureAutoDNS); - page->Custom("DNS server", [host](UI::ViewGroup *parent) -> UI::View * { - return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureDNSServer, T(I18NCat::NETWORKING, "DNS server"), "", 32, host->PageScreenManager())); - }).Disabled(&g_Config.bInfrastructureAutoDNS); - - page->Header("UPnP (port-forwarding)"); - // Only togglable outside a game - sceNet latches settings like UPnPUseOriginalPort at boot, - // and a game that's already mapped its ports wouldn't cope with them disappearing. - page->Setting(&g_Config.bEnableUPnP).EnabledFunc([] { - return !PSP_IsInited(); - }).OnChange([requestLocalNetwork](UI::EventParams &e) { - requestLocalNetwork(e); - // Wake the UPnP service thread so it connects (or tears its mappings back down) right - // away, instead of waiting for a port request that may never come. - UPnP_Notify(); - }); - page->Setting(&g_Config.bUPnPUseOriginalPort).Enabled(&g_Config.bEnableUPnP); - page->Hint({ I18NCat::NETWORKING, "UseOriginalPort Tip", "May not work for all devices or games, see wiki." }); - - page->Header("Chat"); - page->Setting(&g_Config.bEnableNetworkChat); - page->Setting(&g_Config.iChatButtonPosition).Enabled(&g_Config.bEnableNetworkChat); - page->Setting(&g_Config.iChatScreenPosition).Enabled(&g_Config.bEnableNetworkChat); - page->Setting(&g_Config.bChatTimestamps).Enabled(&g_Config.bEnableNetworkChat); - - page->Header("Quick chat"); - page->Setting(&g_Config.bEnableQuickChat).Enabled(&g_Config.bEnableNetworkChat); - - for (int i = 0; i < 5; i++) { - page->Custom("Quick chat %1", [host, i](UI::ViewGroup *parent) -> UI::View * { - return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sQuickChat[i], ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Quick chat %1"), i + 1), "", 32, host->PageScreenManager())); - }).EnabledFunc([] { - return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; - }); - } - - page->Header({ I18NCat::NETWORKING, "Misc", "Misc (default = compatibility)" }); - page->Choice("Open PPSSPP Multiplayer Wiki Page", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - std::string url(n->T("MultiplayerHowToURL", "https://github.com/hrydgard/ppsspp/wiki/How-to-play-multiplayer-games-with-PPSSPP")); - System_LaunchUrl(LaunchUrlType::BROWSER_URL, url); - }); - static const char *wlanChannels[] = {"Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}; - page->Setting(&g_Config.iWlanAdhocChannel).Setup([](View *view) { - PopupMultiChoice *wlanChannelChoice = static_cast(view); - wlanChannelChoice->SetChoicesUntranslated(1, (int)ARRAY_SIZE(wlanChannels) - 1); - for (int i = 0; i < 4; i++) { - wlanChannelChoice->HideChoice(i + 2); - wlanChannelChoice->HideChoice(i + 7); - } - }); - page->Setting(&g_Config.iPortOffset); - page->Hint("PortOffsetHint"); - page->Setting(&g_Config.iMinTimeout); - page->Setting(&g_Config.bForcedFirstConnect); - page->Setting(&g_Config.bAllowSpeedControlWhileConnected); - if (Discord::IsAvailable()) { - page->Setting(&g_Config.bDiscordRichPresence); - } -} - -static void BuildToolsSettings(SettingsPage *page) { - using namespace UI; - SettingsPageHost *host = page->Host(); - - page->Header("Tools"); - - const bool showRetroAchievements = System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_VR; - if (showRetroAchievements) { - page->Choice({ I18NCat::SYSTEM, "RetroAchievements" }).OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new RetroAchievementsSettingsScreen(host->PageGamePath())); - }).Setup([](View *view) { - static_cast(view)->SetIconRight(ImageID("I_RETROACHIEVEMENTS_LOGO")); - }); - } - - // These were moved here so use the wrong translation objects, to avoid having to change all inis... This isn't a sustainable situation :P - page->Choice({ I18NCat::SAVEDATA, "Savedata Manager" }).OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new SavedataScreen(host->PageGamePath())); - }); - page->Choice({ I18NCat::DEVELOPER, "System Information" }).OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new SystemInfoScreen(host->PageGamePath())); - }); - page->Link({ I18NCat::SYSTEM, "Developer Tools" }, I18NCat::DEVELOPER, &BuildDeveloperToolsPage); - page->Choice({ I18NCat::REMOTEISO, "Remote disc streaming" }).OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new RemoteISOScreen(host->PageGamePath())); - }); - page->Choice({ I18NCat::SYSTEM, "PSP Firmware" }).OnClick([host](UI::EventParams &) { - host->PageScreenManager()->push(new FirmwareScreen(host->PageGamePath())); - }); -} - void GameSettingsScreen::BuildSystemSettings(SettingsPage *page) { using namespace UI; @@ -1403,33 +526,6 @@ void GameSettingsScreen::BuildSystemSettings(SettingsPage *page) { #endif } -static void BuildVRSettings(SettingsPage *page) { - using namespace UI; - - int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); - - if (deviceType == DEVICE_TYPE_VR) { - page->Header("Virtual reality"); - page->Setting(&g_Config.bEnableVR); - page->Setting(&g_Config.bEnable6DoF); - page->Setting(&g_Config.bEnableStereo); - page->Setting(&g_Config.bEnableImmersiveVR); - if (IsPassthroughSupported()) { - page->Setting(&g_Config.bPassthrough); - } - page->Setting(&g_Config.bForce72Hz); - } - - page->Header("VR camera"); - if (deviceType == DEVICE_TYPE_VR) { - page->Setting(&g_Config.fCanvasDistance); - page->Setting(&g_Config.fCanvas3DDistance); - } - page->Setting(&g_Config.fFieldOfViewPercentage); - page->Setting(&g_Config.bRescaleHUD); - page->Setting(&g_Config.fHeadUpDisplayScale).Enabled(&g_Config.bRescaleHUD); - page->Setting(&g_Config.bManualForceVR); -} void GameSettingsScreen::OnImmersiveModeChange(UI::EventParams &e) { System_Notify(SystemNotification::IMMERSIVE_MODE_CHANGE); if (g_Config.iAndroidHwScale != 0) { diff --git a/UI/Settings/AudioPage.cpp b/UI/Settings/AudioPage.cpp new file mode 100644 index 0000000000..7b16ac84be --- /dev/null +++ b/UI/Settings/AudioPage.cpp @@ -0,0 +1,182 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include + +#include "ppsspp_config.h" + +#include "Common/Audio/AudioBackend.h" +#include "Common/Data/Text/I18n.h" +#include "Common/Log.h" +#include "Common/StringUtils.h" +#include "Common/System/System.h" +#include "Common/TimeUtil.h" +#include "Common/UI/Root.h" +#include "Common/UI/View.h" +#include "Core/Config.h" +#include "Core/ConfigValues.h" +#include "Core/HLE/sceUsbMic.h" +#include "UI/BackgroundAudio.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" + +#if PPSSPP_PLATFORM(ANDROID) +#include "android/jni/AndroidAudio.h" + +extern AndroidAudioState *g_audioState; +#endif + +void BuildAudioSettings(SettingsPage *page) { + using namespace UI; + +#if PPSSPP_PLATFORM(IOS) + page->Setting(&g_Config.bAudioRespectSilentMode).Enabled(&g_Config.bEnableSound); +#endif +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) + page->Setting(&g_Config.bAudioMixWithOthers).Enabled(&g_Config.bEnableSound); +#endif + page->Header("Audio playback"); + + page->Setting(&g_Config.iAudioPlaybackMode); + page->Setting(&g_Config.bFillAudioGaps).EnabledFunc([]() { + return g_Config.iAudioPlaybackMode == (int)AudioSyncMode::GRANULAR; + }); + + page->Header("Game volume"); + + // This is here because it now only applies to in-game. Muting the menu sounds is separate. + page->Setting(&g_Config.bEnableSound); + + page->Setting(&g_Config.iGameVolume).Enabled(&g_Config.bEnableSound); + page->Setting(&g_Config.iReverbVolume).Enabled(&g_Config.bEnableSound); + page->Setting(&g_Config.iAltSpeedVolume).Enabled(&g_Config.bEnableSound); + page->Setting(&g_Config.iAchievementVolume).Enabled(&g_Config.bEnableSound).OnChange([](EventParams &e) { + // Audio preview + float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume); + g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume); + }); + + page->Header(); + + page->Setting(&g_Config.bUISound); + page->Setting(&g_Config.iUIVolume).LiveUpdate().Enabled(&g_Config.bUISound).OnChange([](EventParams &e) { + static double lastTimePlayed = 0.0; + double now = time_now_d(); + if (now - lastTimePlayed < 0.1) { + return; // Don't play if we just played one, to avoid spamming when dragging. + } + lastTimePlayed = now; + // Audio preview + PlayUISound(UI::UISound::CONFIRM); + }); + + page->Setting(&g_Config.iGamePreviewVolume); + + bool sdlAudio = false; + +#if defined(SDL) + page->Header("Audio backend"); + std::vector audioDeviceList; + SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList); + audioDeviceList.insert(audioDeviceList.begin(), std::string(T(I18NCat::AUDIO, "Auto"))); + page->Setting(&g_Config.sAudioDevice, std::move(audioDeviceList)).OnChange([](EventParams &e) { + if (g_Config.sAudioDevice == T(I18NCat::AUDIO, "Auto")) { + g_Config.sAudioDevice.clear(); + } + System_Notify(SystemNotification::AUDIO_RESET_DEVICE); + }); + sdlAudio = true; + + static const int bufferSizes[] = {128, 256, 512, 1024, 2048}; + page->IntSlider(&g_Config.iSDLAudioBufferSize, 0, 2048, "Buffer size").Default(256).RestrictTo(bufferSizes, ARRAY_SIZE(bufferSizes)); + page->Hint({ I18NCat::DIALOG, "This change will not take effect until PPSSPP is restarted." }); +#endif + +#if PPSSPP_PLATFORM(WINDOWS) + extern AudioBackend *g_audioBackend; + + std::vector audioDeviceNames; + std::vector audioDeviceIds; + + std::vector deviceDescs; + g_audioBackend->EnumerateDevices(&deviceDescs); + if (!deviceDescs.empty()) { + page->Header("Audio backend"); + for (auto &desc : deviceDescs) { + audioDeviceNames.push_back(desc.name); + audioDeviceIds.push_back(desc.uniqueId); + } + + audioDeviceNames.insert(audioDeviceNames.begin(), std::string(T(I18NCat::AUDIO, "Auto"))); + audioDeviceIds.insert(audioDeviceIds.begin(), ""); + + page->Setting(&g_Config.sAudioDevice, std::move(audioDeviceNames), std::move(audioDeviceIds)).OnChange([](EventParams &) { + bool reverted; + if (g_audioBackend->InitOutputDevice(g_Config.sAudioDevice, LatencyMode::Aggressive, &reverted)) { + if (reverted) { + WARN_LOG(Log::Audio, "Unexpected: After a direct choice, audio device reverted to default. '%s'", g_Config.sAudioDevice.c_str()); + } + } else { + WARN_LOG(Log::Audio, "InitOutputDevice failed"); + } + }); + page->Setting(&g_Config.bAutoSwitchAudioDevice).EnabledFunc([]()->bool { + return g_Config.sAudioDevice.empty(); + }); + } + +#else + if (sdlAudio) { + page->Setting(&g_Config.bAutoSwitchAudioDevice); + } else { + page->Header("Audio backend"); + } + +#if PPSSPP_PLATFORM(ANDROID) + page->Setting(&g_Config.bExtraAudioBuffering); + + auto restartAudio = [](UI::EventParams &) { + AndroidAudio_Restart(g_audioState); + }; + // AAudio isn't stable before Android 8.1, so there it's always OpenSL ES. + if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 27) { + page->Setting(&g_Config.iAndroidAudioApi).OnChange(restartAudio); + page->Hint({ I18NCat::AUDIO, "AudioApiHint", "Try the other one if you have no sound or other audio problems." }); + + page->Setting(&g_Config.bAndroidAudioMMap).OnChange(restartAudio).EnabledFunc([]() { + return g_Config.iAndroidAudioApi == (int)AndroidAudioApi::AAUDIO; + }); + page->Hint({ I18NCat::AUDIO, "AudioMMapHint", "Lowest audio delay. On some devices, it causes silence over Bluetooth or other connected audio devices. Turn it off if you get no sound." }); + } + // Show Oboe debug info + const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState); + if (!audioErrorStr.empty()) { + page->Info("Audio Error", audioErrorStr); + } +#endif +#endif + + std::vector micList = Microphone::getDeviceList(); + if (!micList.empty()) { + page->Header("Microphone"); + std::vector micValues = micList; + micList.emplace(micList.begin(), T(I18NCat::AUDIO, "Auto")); + micValues.insert(micValues.begin(), ""); + page->Setting(&g_Config.sMicDevice, std::move(micList), std::move(micValues)); + } +} diff --git a/UI/Settings/ControlsPage.cpp b/UI/Settings/ControlsPage.cpp new file mode 100644 index 0000000000..2ca334fc0c --- /dev/null +++ b/UI/Settings/ControlsPage.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include + +#include "ppsspp_config.h" + +#include "Common/Data/Text/I18n.h" +#include "Common/System/Display.h" +#include "Common/System/System.h" +#include "Common/UI/ScreenManager.h" +#include "Common/UI/View.h" +#include "Core/Config.h" +#include "Core/ConfigValues.h" +#include "UI/ControlMappingScreen.h" +#include "UI/GameSettingsScreen.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" +#include "UI/TiltAnalogSettingsScreen.h" +#include "UI/TouchControlLayoutScreen.h" + +void BuildControlsSettings(SettingsPage *page) { + using namespace UI; + SettingsPageHost *host = page->Host(); + + int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); + + page->Header({ I18NCat::MAINSETTINGS, "Controls" }); + page->Choice("Control mapping").OnClick([host](UI::EventParams &e) { + host->PageScreenManager()->push(new ControlMappingScreen(host->PageGamePath())); + }); + page->Choice("Calibrate analog stick").OnClick([host](UI::EventParams &e) { + host->PageScreenManager()->push(new AnalogCalibrationScreen(host->PageGamePath())); + }); + +#if (PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID)) + page->Setting(&g_Config.bSystemControls); +#endif +#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + page->Setting(&g_Config.bGamepadOnlyFocused); +#endif + + if (System_GetPropertyBool(SYSPROP_HAS_ACCELEROMETER)) { + page->Custom("Tilt control setup", [host](UI::ViewGroup *parent) -> UI::View * { + // Show the tilt type on the item. + Choice *customizeTilt = parent->Add(new ChoiceWithCallbackValueDisplay(T(I18NCat::CONTROLS, "Tilt control setup"), []() -> std::string { + if (g_Config.bTiltInputEnabled && (u32)g_Config.iTiltInputType < (u32)g_numTiltTypes) { + auto co = GetI18NCategory(I18NCat::CONTROLS); + return std::string(co->T(g_tiltTypes[g_Config.iTiltInputType])); + } else { + auto di = GetI18NCategory(I18NCat::DIALOG); + return std::string(di->T("Disabled")); + } + })); + customizeTilt->OnClick.Add([host](UI::EventParams &e) { + host->PageScreenManager()->push(new TiltAnalogSettingsScreen(host->PageGamePath())); + }); + return customizeTilt; + }); + } else if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_VR) { // TODO: This seems like a regression + page->Setting(&g_Config.bHapticFeedback); + } + + page->Header("Analog to digital mapping"); + page->Setting(&g_Config.fAnalogTriggerThreshold); + page->Setting(&g_Config.fAnalogStickThreshold); + + // TVs don't have touch control, at least not yet. + if ((deviceType != DEVICE_TYPE_TV) && (deviceType != DEVICE_TYPE_VR)) { + page->Header("On-screen touch controls"); + page->Setting(&g_Config.bShowTouchControls); + page->Choice("Edit touch control layout").OnClick([host](UI::EventParams &e) { + host->PageScreenManager()->push(new TouchControlLayoutScreen(host->PageGamePath())); + }).Enabled(&g_Config.bShowTouchControls); + page->Choice("Gesture mapping").OnClick([host](UI::EventParams &e) { + host->PageScreenManager()->push(new GestureMappingScreen(host->PageGamePath())); + }).Enabled(&g_Config.bShowTouchControls); + + page->Setting(&g_Config.iTouchButtonStyle).Enabled(&g_Config.bShowTouchControls); + + page->Setting(&g_Config.iTouchButtonOpacity).Enabled(&g_Config.bShowTouchControls); + page->Setting(&g_Config.iTouchButtonHideSeconds).Enabled(&g_Config.bShowTouchControls); + + page->Setting(&g_Config.bTouchGliding).Enabled(&g_Config.bShowTouchControls); + + TouchControlConfig &touch = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); + + // Hide stick background, useful when increasing the size + page->Setting(&touch.bHideStickBackground).Enabled(&g_Config.bShowTouchControls); + + // Sticky D-pad. + page->Setting(&g_Config.bStickyTouchDPad).Enabled(&g_Config.bShowTouchControls); + page->Hint("Easier sweeping movements"); + + // Re-centers itself to the touch location on touch-down. + page->Setting(&g_Config.bAutoCenterTouchAnalog).Enabled(&g_Config.bShowTouchControls); + + if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { + page->Setting(&g_Config.bHapticFeedback); + } + + // The pause button is now a regular on-screen button. + + page->Setting(&g_Config.bDisableDpadDiagonals).Enabled(&g_Config.bShowTouchControls); + } + +#if PPSSPP_PLATFORM(WINDOWS) + page->Header("Control input sources"); + page->Setting(&g_Config.bAllowHIDInput); + page->Setting(&g_Config.bAllowXInput); + page->Setting(&g_Config.bAllowDInput); +#endif + + if (deviceType != DEVICE_TYPE_VR) { + page->Header({ I18NCat::CONTROLS, "Keyboard", "Keyboard Control Settings" }); +#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + page->Setting(&g_Config.bIgnoreWindowsKey); +#endif + page->Setting(&g_Config.fAnalogLimiterDeadzone); + page->Hint({ I18NCat::CONTROLS, "AnalogLimiter Tip", "When the analog limiter button is pressed" }); + page->Setting(&g_Config.iRapidFireInterval); +#if PPSSPP_PLATFORM(WINDOWS) || defined(SDL) || PPSSPP_PLATFORM(ANDROID) + bool enableMouseSettings = true; +#if PPSSPP_PLATFORM(ANDROID) + if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) < 12) { + enableMouseSettings = false; + } +#endif +#else + bool enableMouseSettings = false; +#endif + if (enableMouseSettings) { + // The mousewheel button-release setting is independent of actual mouse delta control. + page->Header({ I18NCat::CONTROLS, "Mouse", "Mouse settings" }); + page->Setting(&g_Config.iMouseWheelUpDelayMs); + + page->Setting(&g_Config.bMouseControl); + page->Hint({ I18NCat::CONTROLS, "MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon." }); + +#if !PPSSPP_PLATFORM(ANDROID) + page->Setting(&g_Config.bMouseConfine).Enabled(&g_Config.bMouseControl); +#endif + page->Setting(&g_Config.fMouseSensitivity).Enabled(&g_Config.bMouseControl).LiveUpdate(); + page->Setting(&g_Config.fMouseSmoothing).Enabled(&g_Config.bMouseControl).LiveUpdate(); + } + } +} diff --git a/UI/Settings/GraphicsPage.cpp b/UI/Settings/GraphicsPage.cpp new file mode 100644 index 0000000000..50fa61ff23 --- /dev/null +++ b/UI/Settings/GraphicsPage.cpp @@ -0,0 +1,381 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include + +#include "ppsspp_config.h" + +#include "Common/BitSet.h" +#include "Common/Data/Text/I18n.h" +#include "Common/GPU/thin3d.h" +#include "Common/System/Display.h" +#include "Common/System/NativeApp.h" +#include "Common/System/System.h" +#include "Common/UI/PopupScreens.h" +#include "Common/UI/ScreenManager.h" +#include "Common/UI/View.h" +#include "Core/Config.h" +#include "Core/ConfigValues.h" +#include "Core/Instance.h" +#include "Core/KeyMap.h" +#include "Core/System.h" +#include "GPU/Common/PostShader.h" +#include "UI/DevScreens.h" +#include "UI/DisplayLayoutScreen.h" +#include "UI/MiscScreens.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" + +static bool UsingHardwareTextureScaling() { + return g_Config.bTexHardwareScaling && (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::METAL) && !g_Config.bSoftwareRendering; +} + +static std::string TextureTranslateName(std::string_view value) { + const TextureShaderInfo *info = GetTextureShaderInfo(value); + if (info) { + auto ts = GetI18NCategory(I18NCat::TEXTURESHADERS); + return std::string(ts->T(value, info->name.c_str())); + } else { + return std::string(value); + } +} + +static std::string *GPUDeviceNameSetting() { + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { + return &g_Config.sVulkanDevice; + } +#ifdef _WIN32 + if (g_Config.iGPUBackend == (int)GPUBackend::DIRECT3D11) { + return &g_Config.sD3D11Device; + } +#endif + return nullptr; +} + +// TODO: Make this generic +extern int DefaultDepthRaster(); + +static void OnRenderingBackend(SettingsPageHost *host) { + // It only makes sense to show the restart prompt if the backend was actually changed. + if (g_Config.iGPUBackend != (int)GetGPUBackend()) { + host->RestartOrDo([]() { + g_Config.iGPUBackend = (int)GetGPUBackend(); + }); + } +} + +static void OnRenderingDevice(SettingsPageHost *host) { + // It only makes sense to show the restart prompt if the device was actually changed. + std::string *deviceNameSetting = GPUDeviceNameSetting(); + if (deviceNameSetting && *deviceNameSetting != GetGPUBackendDevice()) { + host->RestartOrDo([host]() { + std::string *deviceNameSetting = GPUDeviceNameSetting(); + if (deviceNameSetting) + *deviceNameSetting = GetGPUBackendDevice(); + // Needed to redraw the setting. + host->RecreatePageViews(); + }); + } +} + +void BuildGraphicsSettings(SettingsPage *page) { + using namespace UI; + SettingsPageHost *host = page->Host(); + + Draw::DrawContext *draw = host->PageScreenManager()->getDrawContext(); + int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); + + page->Header("Rendering Mode"); + +#if !PPSSPP_PLATFORM(UWP) + page->Setting(&g_Config.iGPUBackend).Setup([host](View *view) { + PopupMultiChoice *renderingBackendChoice = static_cast(view); + renderingBackendChoice->SetPreOpenCallback([host](UI::PopupMultiChoice *choice) { + // Don't filter until the last possible moment, since it involves trying to initialize Vulkan, if we were + // started in OpenGL mode on Android. + choice->HideChoice(1); + choice->OnChoice.Add([host](UI::EventParams &) { + OnRenderingBackend(host); + }); + + if (!g_Config.IsBackendEnabled(GPUBackend::OPENGL)) + choice->HideChoice((int)GPUBackend::OPENGL); + if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D11)) + choice->HideChoice((int)GPUBackend::DIRECT3D11); + if (!g_Config.IsBackendEnabled(GPUBackend::VULKAN)) + choice->HideChoice((int)GPUBackend::VULKAN); + if (!g_Config.IsBackendEnabled(GPUBackend::METAL)) + choice->HideChoice((int)GPUBackend::METAL); + }); + + if (!IsFirstInstance()) { + // If we're not the first instance, can't save the setting, and it requires a restart, so... + renderingBackendChoice->SetEnabled(false); + } + }); +#endif + +#if (PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(TVOS)) || PPSSPP_PLATFORM(ANDROID) + // Showing the game there needs a second swapchain. Otherwise the system mirrors the screen. + page->Setting(&g_Config.iExternalDisplay).EnabledFunc([draw] { + return draw->GetDeviceCaps().multipleSwapchains; + }).OnChange([previous = g_Config.iExternalDisplay](UI::EventParams &e) mutable { + // Picking the current value again would rebuild the TV's output for nothing (iOS starts over on a change). + if (g_Config.iExternalDisplay == previous) { + return; + } + previous = g_Config.iExternalDisplay; + System_Notify(SystemNotification::EXTERNAL_DISPLAY_CHANGED); + }); +#endif + + // Backends that don't allow a device choice will only expose one device. + if (draw->GetDeviceList().size() > 1) { + std::string *deviceNameSetting = GPUDeviceNameSetting(); + if (deviceNameSetting) { + page->Setting(deviceNameSetting, draw->GetDeviceList()).OnChange([host](UI::EventParams &e) { + OnRenderingDevice(host); + }); + } + } + + page->Setting(&g_Config.iInternalResolution).EnabledFunc([] { + return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects; + }); + + if (deviceType != DEVICE_TYPE_VR) { + page->Setting(&g_Config.bSoftwareRendering).EnabledFunc([] { + return !PSP_IsInited(); + }); + } + + if (draw->GetDeviceCaps().multiSampleLevelsMask != 1) { + page->Setting(&g_Config.iMultiSampleLevel).EnabledFunc([] { + return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects; + }).Setup([draw](View *view) { + PopupMultiChoice *msaaChoice = static_cast(view); + if (g_Config.iMultiSampleLevel > 1 && draw->GetDeviceCaps().isTilingGPU) { + msaaChoice->SetIconRight(ImageID("I_WARNING"), 0.7f); + } + // Hide unsupported levels. + for (int i = 1; i < msaaChoice->NumChoices(); i++) { + if ((draw->GetDeviceCaps().multiSampleLevelsMask & (1 << i)) == 0) { + msaaChoice->HideChoice(i); + } else if (i > 0 && draw->GetDeviceCaps().isTilingGPU) { + msaaChoice->SetChoiceIcon(i, ImageID("I_WARNING")); + } + } + }); + } + +#if PPSSPP_PLATFORM(ANDROID) + if ((deviceType != DEVICE_TYPE_TV) && (deviceType != DEVICE_TYPE_VR)) { + int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 2; + if (max_res == 3) + max_res = 4; // At least allow 2x + page->Setting(&g_Config.iAndroidHwScale).Setup([max_res](View *view) { + // Hide the scales the display is too small for. + PopupMultiChoice *choice = static_cast(view); + for (int i = max_res; i < choice->NumChoices(); i++) { + choice->HideChoice(i); + } + }); + } +#endif + + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); + + page->Setting(&g_Config.bReplaceTextures); + + page->Header("Display"); + + if (deviceType != DEVICE_TYPE_VR) { +#if !defined(MOBILE_DEVICE) + page->Setting(&g_Config.bFullScreen); + if (draw->GetDeviceCaps().fullScreenExclusiveSupported) { + page->Setting(&g_Config.bAllowFullScreenExclusive).EnabledFunc([] { + return g_Config.bFullScreen && !g_Config.bFullScreenMulti; + }).OnChange([host](UI::EventParams &e) { + host->RestartOrDo([host]() { + g_Config.bAllowFullScreenExclusive = !g_Config.bAllowFullScreenExclusive; + host->RecreatePageViews(); + }); + }); + } + if (System_GetPropertyInt(SYSPROP_DISPLAY_COUNT) > 1) { + page->Setting(&g_Config.bFullScreenMulti).EnabledFunc([] { + return g_Config.bFullScreen && !g_Config.bAllowFullScreenExclusive; + }); + } +#endif + // Display Layout Editor: To avoid overlapping touch controls on large tablets, meet geeky demands for integer zoom/unstretched image etc. + page->Choice("Display layout & effects").OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new DisplayLayoutScreen(host->PageGamePath())); + }); + } + + // If only one mode is supported (like FIFO on iOS), no need to show the options. + if (CountSetBits((u32)draw->GetDeviceCaps().presentModesSupported) > 1) { + // Immediate means non-synchronized, tearing. + if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE) { + page->Setting(&g_Config.bVSync).OnChange([](UI::EventParams &e) { + NativeResized(); // TODO: Remove + }); + } + if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::MAILBOX) { + SettingItem &lowLatency = page->Setting(&g_Config.bLowLatencyPresent).OnChange([](UI::EventParams &e) { + NativeResized(); // TODO: Remove + }); + + // If the immediate mode is supported, we can tie low latency present to VSync. + if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE) { + lowLatency.Enabled(&g_Config.bVSync); + } + } + } + + page->Header("Frame Rate Control"); + + page->Setting(&g_Config.iAltSpeedPercent1); + page->Setting(&g_Config.iAltSpeedPercent2); + if (KeyMap::InputMappingsFromPspButton(VIRTKEY_SPEED_ANALOG, nullptr, true)) { + page->Setting(&g_Config.iAnalogAltSpeedPercent); + } + + page->Header({ I18NCat::GRAPHICS, "Speed Hacks", "Speed Hacks (can cause rendering errors!)" }); + + page->Setting(&g_Config.iFrameSkip).EnabledFunc([] { + return !g_Config.bAutoFrameSkip; + }).Setup([](View *view) { + PopupMultiChoice *choice = static_cast(view); + choice->SetChoicesUntranslated(1, choice->NumChoices() - 1); + }); + + page->Setting(&g_Config.bAutoFrameSkip); + + page->Setting(&g_Config.bSkipBufferEffects).Disabled(&g_Config.bSoftwareRendering); + page->Hint({ I18NCat::GRAPHICS, "RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games" }); + + page->Setting(&g_Config.iSkipGPUReadbackMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) { + static_cast(view)->SetDefault(0); + }); + + page->Setting(&g_Config.iDepthRasterMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) { + PopupMultiChoice *depthRasterMode = static_cast(view); + depthRasterMode->SetChoiceIcon(3, ImageID("I_WARNING")); // It's a performance trap. + depthRasterMode->SetDefault(DefaultDepthRaster()); + if (g_Config.iDepthRasterMode != 3) + depthRasterMode->HideChoice(3); + }); + + page->Setting(&g_Config.iBloomHack).EnabledFunc([] { + return !g_Config.bSoftwareRendering && g_Config.iInternalResolution != 1; + }); + page->Hint("Reduces artifacts"); + + page->Header("Performance"); + page->Setting(&g_Config.bRenderDuplicateFrames).EnabledFunc([] { + return !g_Config.bSkipBufferEffects && g_Config.iFrameSkip == 0; + }); + page->Hint({ I18NCat::GRAPHICS, "RenderDuplicateFrames Tip", "Can make framerate smoother in games that run at lower framerates" }); + + if (draw->GetDeviceCaps().setMaxFrameLatencySupported) { + // Changing it needs a restart, so the value in use is the one the page was built with. + page->Setting(&g_Config.iInflightFrames).OnChange([host, previous = g_Config.iInflightFrames](UI::EventParams &e) { + if (g_Config.iInflightFrames != previous) { + host->RestartOrDo([previous]() { + g_Config.iInflightFrames = previous; + }); + } + }); + page->Hint("Faster, input lag"); // TODO: This hint could use improvement. + } + + page->Header("Texture upscaling"); + + if (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::METAL) { + page->Custom("GPU texture upscaler (fast)", [host](UI::ViewGroup *parent) -> UI::View * { + auto gr = GetI18NCategory(I18NCat::GRAPHICS); + ChoiceWithValueDisplay *textureShaderChoice = parent->Add(new ChoiceWithValueDisplay(&g_Config.sTextureShaderName, gr->T("GPU texture upscaler (fast)"), &TextureTranslateName)); + textureShaderChoice->OnClick.Add([host](UI::EventParams &e) { + auto gr = GetI18NCategory(I18NCat::GRAPHICS); + auto shaderScreen = new TextureShaderScreen(gr->T("GPU texture upscaler (fast)")); + shaderScreen->OnChoice.Add([host](UI::EventParams &e) { + System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); + host->RecreatePageViews(); // Update setting name + g_Config.bTexHardwareScaling = g_Config.sTextureShaderName != "Off"; + }); + if (e.v) + shaderScreen->SetPopupOrigin(e.v); + host->PageScreenManager()->push(shaderScreen); + }); + return textureShaderChoice; + }).Disabled(&g_Config.bSoftwareRendering); + } + + page->Setting(&g_Config.iTexScalingType).EnabledFunc([]() { + return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); + }); + page->Setting(&g_Config.iTexScalingLevel).EnabledFunc([]() { + return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); + }); + page->Hint({ I18NCat::GRAPHICS, "UpscaleLevel Tip", "CPU heavy - some scaling may be delayed to avoid stutter" }); + + page->Setting(&g_Config.bTexDeposterize).EnabledFunc([]() { + return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); + }); + page->Hint({ I18NCat::GRAPHICS, "Deposterize Tip", "Fixes visual banding glitches in upscaled textures" }); + + page->Header("Texture Filtering"); + + page->Setting(&g_Config.iAnisotropyLevel).Disabled(&g_Config.bSoftwareRendering); + + page->Setting(&g_Config.iTexFiltering).Disabled(&g_Config.bSoftwareRendering); + + page->Setting(&g_Config.bSmart2DTexFiltering).Disabled(&g_Config.bSoftwareRendering); + +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) + bool showCardboardSettings = deviceType != DEVICE_TYPE_VR; +#else + // If you enabled it through the ini, you can see this. Useful for testing. + bool showCardboardSettings = config.bEnableCardboardVR; +#endif + if (showCardboardSettings) { + page->Header("Cardboard VR Settings"); + page->Setting(&config.bEnableCardboardVR); + page->Setting(&config.iCardboardScreenSize).Enabled(&config.bEnableCardboardVR); + page->Setting(&config.iCardboardXShift).Enabled(&config.bEnableCardboardVR); + page->Setting(&config.iCardboardYShift).Enabled(&config.bEnableCardboardVR); + } + + page->Header("Overlay Information"); + auto addStatusFlag = [page](ShowStatusFlags flag, const char *text) { + page->Custom(text, [flag, text](UI::ViewGroup *parent) -> UI::View * { + return parent->Add(new BitCheckBox(&g_Config.iShowStatusFlags, (int)flag, T(I18NCat::GRAPHICS, text))); + }); + }; + addStatusFlag(ShowStatusFlags::FPS_COUNTER, "Show FPS Counter"); + addStatusFlag(ShowStatusFlags::SPEED_COUNTER, "Show Speed"); + if (System_GetPropertyBool(SYSPROP_CAN_READ_BATTERY_PERCENTAGE)) { + addStatusFlag(ShowStatusFlags::BATTERY_PERCENT, "Show Battery %"); + } + page->Custom({ I18NCat::DEVELOPER, "Debug overlay" }, [host](UI::ViewGroup *parent) -> UI::View * { + AddOverlayList(parent, host->PageScreenManager()); + return parent->GetViewByIndex(parent->GetNumSubviews() - 1); + }); +} diff --git a/UI/Settings/MainSettingsPages.h b/UI/Settings/MainSettingsPages.h new file mode 100644 index 0000000000..d9b16de9ca --- /dev/null +++ b/UI/Settings/MainSettingsPages.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +namespace UI { +class View; +class ViewGroup; +} + +class SettingsPage; +class SettingsPageHost; + +// The settings screen's tabs, except System, which needs more of the screen. +void BuildGraphicsSettings(SettingsPage *page); +void BuildControlsSettings(SettingsPage *page); +void BuildAudioSettings(SettingsPage *page); +void BuildNetworkingSettings(SettingsPage *page); +void BuildToolsSettings(SettingsPage *page); +void BuildVRSettings(SettingsPage *page); + +// The nickname is shown on both the Networking and System tabs. +UI::View *AddNicknameChoice(SettingsPageHost *host, UI::ViewGroup *parent); diff --git a/UI/Settings/NetworkingPage.cpp b/UI/Settings/NetworkingPage.cpp new file mode 100644 index 0000000000..0ce06862e8 --- /dev/null +++ b/UI/Settings/NetworkingPage.cpp @@ -0,0 +1,270 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include + +#include "Common/Data/Text/I18n.h" +#include "Common/File/Path.h" +#include "Common/StringUtils.h" +#include "Common/System/Request.h" +#include "Common/System/System.h" +#include "Common/UI/Notice.h" +#include "Common/UI/PopupScreens.h" +#include "Common/UI/ScreenManager.h" +#include "Common/UI/View.h" +#include "Common/UI/ViewGroup.h" +#include "Core/Config.h" +#include "Core/HLE/sceNetAdhoc.h" +#include "Core/System.h" +#include "Core/Util/PortManager.h" +#include "UI/AdhocServerScreen.h" +#include "UI/DiscordIntegration.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" + +// Compound view just like the audio file choosers +class MacAddressChooser : public UI::LinearLayout { +public: + MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr); +}; + +MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { + using namespace UI; + SetSpacing(5.0f); + if (!layoutParams) { + layoutParams_->width = FILL_PARENT; + layoutParams_->height = ITEM_HEIGHT; + } + auto n = GetI18NCategory(I18NCat::NETWORKING); + + std::string initialValue = *value; + Add(new PopupTextInputChoice(token, value, title, g_Config.sMACAddress, 17, screenManager, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) { + // Validate the chosen address, and restore to initialValue if bad. + if (g_Config.sMACAddress.size() != 17) { + // TODO: Alert the user + *value = initialValue; + } + }); + Add(new Choice(n->T("Randomize"), new LinearLayoutParams(WRAP_CONTENT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + auto di = GetI18NCategory(I18NCat::DIALOG); + + std::string_view confirmMessage = n->T("ChangeMacSaveConfirm", "Generate a new MAC address?"); + std::string_view warningMessage = n->T("ChangeMacSaveWarning", "Some games verify the MAC address when loading savedata, so this may break old saves."); + std::string combined = g_Config.sMACAddress + "\n\n" + std::string(confirmMessage) + "\n\n" + std::string(warningMessage); + + auto confirmScreen = new MessagePopupScreen( + title, + combined, di->T("Yes"), di->T("No"), + [](bool success) { + if (success) { + g_Config.sMACAddress = CreateRandMAC(); + } + }); + screenManager->push(confirmScreen); + }); +} + +// The nickname is shown on both the Networking and System tabs. +UI::View *AddNicknameChoice(SettingsPageHost *host, UI::ViewGroup *parent) { + using namespace UI; + + auto sy = GetI18NCategory(I18NCat::SYSTEM); + PopupTextInputChoice *nickname = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, host->PageScreenManager())); + if (g_Config.sNickName == "PPSSPP") { + nickname->SetIconRight(ImageID("I_WARNING")); + } + nickname->OnChange.Add([host](UI::EventParams &e) { + // Copy to infrastructure name if valid and not already set. + if (g_Config.sInfrastructureUsername.empty()) { + if (g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 3, 16)) { + g_Config.sInfrastructureUsername = g_Config.sNickName; + } + } + // Updates the warning icon, and the username notice on the Networking tab. + host->RecreatePageViews(); + }); + return nickname; +} + +void BuildNetworkingSettings(SettingsPage *page) { + using namespace UI; + SettingsPageHost *host = page->Host(); + + page->Header({ I18NCat::MAINSETTINGS, "Networking" }); + + page->Choice("Quick start guide for multiplayer", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + std::string url(n->T("MultiplayerQuickStartURL", "https://www.ppsspp.org/docs/multiplayer/quickstart/")); + System_LaunchUrl(LaunchUrlType::BROWSER_URL, url); + }); + + // Ad hoc play, the built-in server and UPnP all talk to the local network. + auto requestLocalNetwork = [](UI::EventParams &e) { + CheckBox *checkBox = static_cast(e.v); + if (checkBox->Toggled()) { + System_RequestLocalNetworkPermission(); + } + }; + + page->Setting(&g_Config.bEnableWlan).OnChange(requestLocalNetwork); + page->Custom("MAC address", [host](UI::ViewGroup *parent) -> UI::View * { + return parent->Add(new MacAddressChooser(host->PageRequesterToken(), host->PageGamePath(), &g_Config.sMACAddress, T(I18NCat::NETWORKING, "MAC address"), host->PageScreenManager())); + }); + + page->Header("Ad Hoc multiplayer"); + + page->Custom("Ad hoc server address", [host](UI::ViewGroup *parent) -> UI::View * { + auto n = GetI18NCategory(I18NCat::NETWORKING); + auto launchAdhocServerScreen = [host](UI::EventParams &) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + host->PageScreenManager()->push(new AdhocServerScreen(&g_Config.sProAdhocServer, n->T("Ad hoc server address"))); + }; + + if (AdhocServerNameIsCustom()) { + LinearLayout *serverRow = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + serverRow->SetSpacing(0.0f); + serverRow->Add(new ChoiceWithValueDisplay(&g_Config.sProAdhocServer, n->T("Ad hoc server address"), I18NCat::NONE, new LinearLayoutParams(1.0f)))->OnClick.Add(launchAdhocServerScreen); + serverRow->Add(new Choice(ImageID("I_EDIT_TEXT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([host](UI::EventParams &) { + AskToEditCurrentServer(host->PageRequesterToken(), host->PageScreenManager()); + }); + return serverRow; + } + + AdhocServerListEntry entry; + if (!g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &entry)) { + Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(ApplySafeSubstitutions("%1 (%2)", entry.name, entry.host), n->T("Ad hoc server address"))); + choice->OnClick.Add(launchAdhocServerScreen); + return choice; + } + // This case happens when updating from a previous version, as well as clicking on one of the local addresses + Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(g_Config.sProAdhocServer, n->T("Ad hoc server address"))); + choice->OnClick.Add(launchAdhocServerScreen); + return choice; + }); + + AdhocServerListEntry serverEntry; + if (!AdhocServerNameIsCustom() && !g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &serverEntry)) { + page->Custom("Server status: %1", [host, serverEntry](UI::ViewGroup *parent) -> UI::View * { + Choice *status = parent->Add(new Choice(ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Server status: %1"), serverEntry.name))); + status->SetIconLeft(ImageID("I_INFO")); + status->OnClick.Add([host, serverEntry](UI::EventParams &) { + host->PageScreenManager()->push(new AdhocServerInfoScreen(serverEntry)); + }); + return status; + }); + } + + page->Setting(&g_Config.iAdhocServerRelayMode).EnabledFunc([] { + return !PSP_IsInited(); + }); + page->Hint({ I18NCat::NETWORKING, "PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower." }); + + page->Custom({ I18NCat::SYSTEM, "Nickname" }, [host](UI::ViewGroup *parent) { + return AddNicknameChoice(host, parent); + }); + page->Hint("Try to pick a unique nickname for ad hoc play"); + + page->Setting(&g_Config.bEnableAdhocServer).OnChange(requestLocalNetwork); + + page->Setting(&g_Config.bAdhocUseUdp).EnabledFunc([] { + return !PSP_IsInited(); + }); + page->Hint("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."); + + page->Setting(&g_Config.bAdhocRelayModeEnetPdpReliable).EnabledFunc([] { + return !PSP_IsInited() && g_Config.bAdhocUseUdp; + }); + page->Hint("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means."); + + page->Header("Infrastructure"); + if (g_Config.sInfrastructureUsername.empty()) { + page->Custom("To play in Infrastructure Mode, you must enter a username", [](UI::ViewGroup *parent) -> UI::View * { + NoticeView *notice = parent->Add(new NoticeView(NoticeLevel::WARN, T(I18NCat::NETWORKING, "To play in Infrastructure Mode, you must enter a username"), "")); + notice->SetWrapText(true); + return notice; + }); + } + page->Custom({ I18NCat::DIALOG, "Username" }, [host](UI::ViewGroup *parent) -> UI::View * { + PopupTextInputChoice *usernameChoice = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureUsername, T(I18NCat::DIALOG, "Username"), "", 16, host->PageScreenManager())); + usernameChoice->SetRestriction(StringRestriction::AlphaNumDashUnderscore, 3); + usernameChoice->OnChange.Add([host](UI::EventParams &e) { + host->RecreatePageViews(); + }); + return usernameChoice; + }); + + page->Setting(&g_Config.bInfrastructureAutoDNS); + page->Custom("DNS server", [host](UI::ViewGroup *parent) -> UI::View * { + return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureDNSServer, T(I18NCat::NETWORKING, "DNS server"), "", 32, host->PageScreenManager())); + }).Disabled(&g_Config.bInfrastructureAutoDNS); + + page->Header("UPnP (port-forwarding)"); + // Only togglable outside a game - sceNet latches settings like UPnPUseOriginalPort at boot, + // and a game that's already mapped its ports wouldn't cope with them disappearing. + page->Setting(&g_Config.bEnableUPnP).EnabledFunc([] { + return !PSP_IsInited(); + }).OnChange([requestLocalNetwork](UI::EventParams &e) { + requestLocalNetwork(e); + // Wake the UPnP service thread so it connects (or tears its mappings back down) right + // away, instead of waiting for a port request that may never come. + UPnP_Notify(); + }); + page->Setting(&g_Config.bUPnPUseOriginalPort).Enabled(&g_Config.bEnableUPnP); + page->Hint({ I18NCat::NETWORKING, "UseOriginalPort Tip", "May not work for all devices or games, see wiki." }); + + page->Header("Chat"); + page->Setting(&g_Config.bEnableNetworkChat); + page->Setting(&g_Config.iChatButtonPosition).Enabled(&g_Config.bEnableNetworkChat); + page->Setting(&g_Config.iChatScreenPosition).Enabled(&g_Config.bEnableNetworkChat); + page->Setting(&g_Config.bChatTimestamps).Enabled(&g_Config.bEnableNetworkChat); + + page->Header("Quick chat"); + page->Setting(&g_Config.bEnableQuickChat).Enabled(&g_Config.bEnableNetworkChat); + + for (int i = 0; i < 5; i++) { + page->Custom("Quick chat %1", [host, i](UI::ViewGroup *parent) -> UI::View * { + return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sQuickChat[i], ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Quick chat %1"), i + 1), "", 32, host->PageScreenManager())); + }).EnabledFunc([] { + return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; + }); + } + + page->Header({ I18NCat::NETWORKING, "Misc", "Misc (default = compatibility)" }); + page->Choice("Open PPSSPP Multiplayer Wiki Page", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + std::string url(n->T("MultiplayerHowToURL", "https://github.com/hrydgard/ppsspp/wiki/How-to-play-multiplayer-games-with-PPSSPP")); + System_LaunchUrl(LaunchUrlType::BROWSER_URL, url); + }); + static const char *wlanChannels[] = {"Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}; + page->Setting(&g_Config.iWlanAdhocChannel).Setup([](View *view) { + PopupMultiChoice *wlanChannelChoice = static_cast(view); + wlanChannelChoice->SetChoicesUntranslated(1, (int)ARRAY_SIZE(wlanChannels) - 1); + for (int i = 0; i < 4; i++) { + wlanChannelChoice->HideChoice(i + 2); + wlanChannelChoice->HideChoice(i + 7); + } + }); + page->Setting(&g_Config.iPortOffset); + page->Hint("PortOffsetHint"); + page->Setting(&g_Config.iMinTimeout); + page->Setting(&g_Config.bForcedFirstConnect); + page->Setting(&g_Config.bAllowSpeedControlWhileConnected); + if (Discord::IsAvailable()) { + page->Setting(&g_Config.bDiscordRichPresence); + } +} diff --git a/UI/Settings/ToolsPage.cpp b/UI/Settings/ToolsPage.cpp new file mode 100644 index 0000000000..b59f61957c --- /dev/null +++ b/UI/Settings/ToolsPage.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/Data/Text/I18n.h" +#include "Common/System/System.h" +#include "Common/UI/ScreenManager.h" +#include "Common/UI/View.h" +#include "UI/DeveloperToolsScreen.h" +#include "UI/FirmwareScreen.h" +#include "UI/RemoteISOScreen.h" +#include "UI/RetroAchievementScreens.h" +#include "UI/SavedataScreen.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" +#include "UI/SystemInfoScreen.h" + +void BuildToolsSettings(SettingsPage *page) { + using namespace UI; + SettingsPageHost *host = page->Host(); + + page->Header("Tools"); + + const bool showRetroAchievements = System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_VR; + if (showRetroAchievements) { + page->Choice({ I18NCat::SYSTEM, "RetroAchievements" }).OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new RetroAchievementsSettingsScreen(host->PageGamePath())); + }).Setup([](View *view) { + static_cast(view)->SetIconRight(ImageID("I_RETROACHIEVEMENTS_LOGO")); + }); + } + + // These were moved here so use the wrong translation objects, to avoid having to change all inis... This isn't a sustainable situation :P + page->Choice({ I18NCat::SAVEDATA, "Savedata Manager" }).OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new SavedataScreen(host->PageGamePath())); + }); + page->Choice({ I18NCat::DEVELOPER, "System Information" }).OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new SystemInfoScreen(host->PageGamePath())); + }); + page->Link({ I18NCat::SYSTEM, "Developer Tools" }, I18NCat::DEVELOPER, &BuildDeveloperToolsPage); + page->Choice({ I18NCat::REMOTEISO, "Remote disc streaming" }).OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new RemoteISOScreen(host->PageGamePath())); + }); + page->Choice({ I18NCat::SYSTEM, "PSP Firmware" }).OnClick([host](UI::EventParams &) { + host->PageScreenManager()->push(new FirmwareScreen(host->PageGamePath())); + }); +} diff --git a/UI/Settings/VRPage.cpp b/UI/Settings/VRPage.cpp new file mode 100644 index 0000000000..15145666bf --- /dev/null +++ b/UI/Settings/VRPage.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/System/System.h" +#include "Common/VR/PPSSPPVR.h" +#include "Core/Config.h" +#include "UI/SettingsPage.h" +#include "UI/Settings/MainSettingsPages.h" + +void BuildVRSettings(SettingsPage *page) { + using namespace UI; + + int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); + + if (deviceType == DEVICE_TYPE_VR) { + page->Header("Virtual reality"); + page->Setting(&g_Config.bEnableVR); + page->Setting(&g_Config.bEnable6DoF); + page->Setting(&g_Config.bEnableStereo); + page->Setting(&g_Config.bEnableImmersiveVR); + if (IsPassthroughSupported()) { + page->Setting(&g_Config.bPassthrough); + } + page->Setting(&g_Config.bForce72Hz); + } + + page->Header("VR camera"); + if (deviceType == DEVICE_TYPE_VR) { + page->Setting(&g_Config.fCanvasDistance); + page->Setting(&g_Config.fCanvas3DDistance); + } + page->Setting(&g_Config.fFieldOfViewPercentage); + page->Setting(&g_Config.bRescaleHUD); + page->Setting(&g_Config.fHeadUpDisplayScale).Enabled(&g_Config.bRescaleHUD); + page->Setting(&g_Config.bManualForceVR); +} diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 93ce591691..3c5f274ca6 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -78,6 +78,12 @@ + + + + + + @@ -143,6 +149,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index b0db57a106..615dfff21f 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -105,6 +105,24 @@ Screens + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + Screens @@ -275,6 +293,9 @@ Screens + + Settings + Screens @@ -345,6 +366,9 @@ {faee5dce-633b-4ba6-b19d-ea70ee3c1c38} + + {16cf6f47-da0b-421f-99a3-c934d3edc1a4} + {071baa63-c681-4ad6-945b-e126645d1b55} diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index 78af5186f3..fd862458ef 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -126,6 +126,7 @@ + @@ -191,6 +192,12 @@ + + + + + + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index 69a9d96c42..627dd50bab 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -134,6 +134,24 @@ Screens + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + Screens @@ -296,6 +314,9 @@ Screens + + Settings + Screens @@ -328,5 +349,8 @@ {38b8413d-438d-4197-9db8-7bd123fc7431} + + {5898f2e1-ea5b-45dc-a02d-17a4de26650b} + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 8c94abb2e2..40225038a5 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -1025,6 +1025,12 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/MainScreen.cpp \ $(SRC)/UI/TabbedDialogScreen.cpp \ $(SRC)/UI/SettingsPage.cpp \ + $(SRC)/UI/Settings/GraphicsPage.cpp \ + $(SRC)/UI/Settings/ControlsPage.cpp \ + $(SRC)/UI/Settings/AudioPage.cpp \ + $(SRC)/UI/Settings/NetworkingPage.cpp \ + $(SRC)/UI/Settings/ToolsPage.cpp \ + $(SRC)/UI/Settings/VRPage.cpp \ $(SRC)/UI/SettingsPagesDialogScreen.cpp \ $(SRC)/UI/AdhocServerScreen.cpp \ $(SRC)/UI/SimpleDialogScreen.cpp \ From 7fd4029333b660f3e8d91f30591563ac91df3128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 16:01:35 +0100 Subject: [PATCH 2/2] DevTools: Rename DeveloperToolsScreen to DeveloperSettingsPages There's no screen in it any more, only the developer tools' settings pages. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/CMakeLists.txt | 4 ++-- UI/{DeveloperToolsScreen.cpp => DeveloperSettingsPages.cpp} | 2 +- UI/{DeveloperToolsScreen.h => DeveloperSettingsPages.h} | 0 UI/GameSettingsScreen.cpp | 2 +- UI/Settings/ToolsPage.cpp | 2 +- UI/UI.vcxproj | 4 ++-- UI/UI.vcxproj.filters | 4 ++-- UWP/UI_UWP/UI_UWP.vcxproj | 4 ++-- UWP/UI_UWP/UI_UWP.vcxproj.filters | 4 ++-- android/jni/Android.mk | 2 +- docs/WebSocketDebugger.md | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) rename UI/{DeveloperToolsScreen.cpp => DeveloperSettingsPages.cpp} (99%) rename UI/{DeveloperToolsScreen.h => DeveloperSettingsPages.h} (100%) diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 583f1d9ab9..9663b55621 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -78,8 +78,8 @@ list(APPEND UISource GamePacker.cpp GameSettingsScreen.h GameSettingsScreen.cpp - DeveloperToolsScreen.h - DeveloperToolsScreen.cpp + DeveloperSettingsPages.h + DeveloperSettingsPages.cpp DriverManagerScreen.h DriverManagerScreen.cpp GPUDriverTestScreen.h diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperSettingsPages.cpp similarity index 99% rename from UI/DeveloperToolsScreen.cpp rename to UI/DeveloperSettingsPages.cpp index d6bfc3e426..8cbb1a33f9 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperSettingsPages.cpp @@ -41,7 +41,7 @@ #include "Core/Util/PathUtil.h" #include "Core/FileSystems/VirtualDiscFileSystem.h" #include "UI/GPUDriverTestScreen.h" -#include "UI/DeveloperToolsScreen.h" +#include "UI/DeveloperSettingsPages.h" #include "UI/DevScreens.h" #include "UI/DriverManagerScreen.h" #include "UI/DisplayLayoutScreen.h" diff --git a/UI/DeveloperToolsScreen.h b/UI/DeveloperSettingsPages.h similarity index 100% rename from UI/DeveloperToolsScreen.h rename to UI/DeveloperSettingsPages.h diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 5b10483104..1c00ae7e99 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -50,7 +50,7 @@ #include "UI/GamepadEmu.h" #include "UI/ControlMappingScreen.h" #include "UI/DevScreens.h" -#include "UI/DeveloperToolsScreen.h" +#include "UI/DeveloperSettingsPages.h" #include "UI/DisplayLayoutScreen.h" #include "UI/FirmwareScreen.h" #include "UI/RemoteISOScreen.h" diff --git a/UI/Settings/ToolsPage.cpp b/UI/Settings/ToolsPage.cpp index b59f61957c..951fea7485 100644 --- a/UI/Settings/ToolsPage.cpp +++ b/UI/Settings/ToolsPage.cpp @@ -19,7 +19,7 @@ #include "Common/System/System.h" #include "Common/UI/ScreenManager.h" #include "Common/UI/View.h" -#include "UI/DeveloperToolsScreen.h" +#include "UI/DeveloperSettingsPages.h" #include "UI/FirmwareScreen.h" #include "UI/RemoteISOScreen.h" #include "UI/RetroAchievementScreens.h" diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 3c5f274ca6..b9d6247c44 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -36,7 +36,7 @@ - + @@ -107,7 +107,7 @@ - + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 615dfff21f..e79229eb59 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -159,7 +159,7 @@ ImDebugger - + Screens @@ -329,7 +329,7 @@ ImDebugger - + Screens diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index fd862458ef..b1e306cead 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -95,7 +95,7 @@ - + @@ -161,7 +161,7 @@ - + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index 627dd50bab..43ce18d962 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -47,7 +47,7 @@ Screens - + Screens @@ -227,7 +227,7 @@ Screens - + Screens diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 40225038a5..b5eae5ad78 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -1054,7 +1054,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/UploadScreen.cpp \ $(SRC)/UI/ControlMappingScreen.cpp \ $(SRC)/UI/GameSettingsScreen.cpp \ - $(SRC)/UI/DeveloperToolsScreen.cpp \ + $(SRC)/UI/DeveloperSettingsPages.cpp \ $(SRC)/UI/GPUDriverTestScreen.cpp \ $(SRC)/UI/TiltAnalogSettingsScreen.cpp \ $(SRC)/UI/TouchControlLayoutScreen.cpp \ diff --git a/docs/WebSocketDebugger.md b/docs/WebSocketDebugger.md index 6f9f9cae0e..8636984274 100644 --- a/docs/WebSocketDebugger.md +++ b/docs/WebSocketDebugger.md @@ -294,7 +294,7 @@ file - this is just an index. ## Enabling it - **UI**: Settings > Tools > Developer Tools > "Allow remote debugger" - checkbox (`UI/DeveloperToolsScreen.cpp`). The "Local Server Port" slider on + checkbox (`UI/DeveloperSettingsPages.cpp`). The "Local Server Port" slider on the Networking screen sets the port (shared with Remote ISO sharing; `0` = auto-pick). - **Config**: `RemoteDebuggerOnStartup=true` in `ppsspp.ini`