diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index a1041fd2d1..9663b55621 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 @@ -71,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 a032f247c7..1c00ae7e99 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -44,12 +44,13 @@ #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" #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" @@ -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..951fea7485 --- /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/DeveloperSettingsPages.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..b9d6247c44 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -36,7 +36,7 @@ - + @@ -78,6 +78,12 @@ + + + + + + @@ -101,7 +107,7 @@ - + @@ -143,6 +149,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index b0db57a106..e79229eb59 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -105,6 +105,24 @@ Screens + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + Screens @@ -141,7 +159,7 @@ ImDebugger - + Screens @@ -275,6 +293,9 @@ Screens + + Settings + Screens @@ -308,7 +329,7 @@ ImDebugger - + 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..b1e306cead 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -95,7 +95,7 @@ - + @@ -126,6 +126,7 @@ + @@ -160,7 +161,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..43ce18d962 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -47,7 +47,7 @@ Screens - + Screens @@ -134,6 +134,24 @@ Screens + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + Screens @@ -209,7 +227,7 @@ Screens - + 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..b5eae5ad78 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 \ @@ -1048,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`