diff --git a/CMakeLists.txt b/CMakeLists.txt index 6524222aa..eb642a517 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1096,10 +1096,6 @@ set(IMGUI src/imgui/imgui_config.h src/imgui/renderer/imgui_core.h src/imgui/renderer/imgui_impl_sdl3.cpp src/imgui/renderer/imgui_impl_sdl3.h - src/imgui/renderer/imgui_impl_sdl3_bpm.cpp - src/imgui/renderer/imgui_impl_sdl3_bpm.h - src/imgui/renderer/imgui_impl_sdlrenderer3.cpp - src/imgui/renderer/imgui_impl_sdlrenderer3.h src/imgui/renderer/imgui_impl_vulkan.cpp src/imgui/renderer/imgui_impl_vulkan.h src/imgui/renderer/font_data.cpp @@ -1108,10 +1104,16 @@ set(IMGUI src/imgui/imgui_config.h src/imgui/renderer/font_stack.h src/imgui/renderer/texture_manager.cpp src/imgui/renderer/texture_manager.h - src/imgui/big_picture.cpp - src/imgui/big_picture.h - src/imgui/settings_dialog_imgui.cpp - src/imgui/settings_dialog_imgui.h + src/imgui/big_picture/big_picture.cpp + src/imgui/big_picture/big_picture.h + src/imgui/big_picture/settings_dialog_imgui.cpp + src/imgui/big_picture/settings_dialog_imgui.h + src/imgui/big_picture/settings_dialog_layer.cpp + src/imgui/big_picture/settings_dialog_layer.h + src/imgui/big_picture/imgui_impl_sdl3_big_picture.cpp + src/imgui/big_picture/imgui_impl_sdl3_big_picture.h + src/imgui/big_picture/imgui_impl_sdlrenderer3.cpp + src/imgui/big_picture/imgui_impl_sdlrenderer3.h ) set(INPUT src/input/controller.cpp @@ -1274,7 +1276,6 @@ cmrc_add_resource_library(embedded-resources NAMESPACE res src/images/big_picture/folder.png src/images/big_picture/settings.png - src/images/big_picture/global-settings.png src/images/big_picture/experimental.png src/images/big_picture/graphics.png src/images/big_picture/controller.png diff --git a/REUSE.toml b/REUSE.toml index c59a358c7..741784afc 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -79,7 +79,6 @@ path = [ "src/images/big_picture/controller.png", "src/images/big_picture/experimental.png", "src/images/big_picture/folder.png", - "src/images/big_picture/global-settings.png", "src/images/big_picture/graphics.png", "src/images/big_picture/log.png", "src/images/big_picture/settings.png", diff --git a/src/images/big_picture/global-settings.png b/src/images/big_picture/global-settings.png deleted file mode 100644 index 47d384f7d..000000000 Binary files a/src/images/big_picture/global-settings.png and /dev/null differ diff --git a/src/imgui/big_picture.h b/src/imgui/big_picture.h deleted file mode 100644 index 56b6b327c..000000000 --- a/src/imgui/big_picture.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace BigPictureMode { - -struct Game { - SDL_Texture* iconTexture; - std::filesystem::path ebootPath; - std::string title; - std::string serial; - bool focusState; -}; - -void Launch(char* executableName); -void SetGameIcons(std::vector& games); -void GetGameInfo(std::vector& games, bool AddGlobalSettings, SDL_Texture* texture = {}); -std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem::path game_folder); - -void LoadTextureDataFromFile(std::filesystem::path filePath, SDL_Texture*& texture, - SDL_Renderer* renderer); -void LoadTextureData(std::vector data, SDL_Texture*& texture, SDL_Renderer* renderer); - -} // namespace BigPictureMode diff --git a/src/imgui/big_picture.cpp b/src/imgui/big_picture/big_picture.cpp similarity index 71% rename from src/imgui/big_picture.cpp rename to src/imgui/big_picture/big_picture.cpp index 108d4f2b3..e2e2aeb66 100644 --- a/src/imgui/big_picture.cpp +++ b/src/imgui/big_picture/big_picture.cpp @@ -2,8 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include #include #include "big_picture.h" @@ -14,24 +12,181 @@ #include "emulator.h" #include "imgui/imgui_std.h" #include "imgui/renderer/font_stack.h" -#include "imgui/renderer/imgui_impl_sdl3_bpm.h" -#include "imgui/renderer/imgui_impl_sdlrenderer3.h" +#include "imgui_impl_sdl3_big_picture.h" +#include "imgui_impl_sdlrenderer3.h" #include "settings_dialog_imgui.h" -CMRC_DECLARE(res); - namespace BigPictureMode { -const float gameImageSize = 200.f; +constexpr float gameImageSize = 200.f; -static bool done = false; -static bool showSettings = false; +bool done = false; +bool showSettings = false; -static std::filesystem::path runEbootPath = ""; -static std::vector gameVec = {}; +std::filesystem::path runEbootPath = ""; +std::vector gameIcons = {}; -static float uiScale = 1.0f; -static SDL_Renderer* renderer; +float uiScale = 1.0f; +SDL_Renderer* renderer; + +namespace { + +std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem::path game_folder) { + std::filesystem::path outputPath; + auto update_folder = game_folder; + update_folder += "-UPDATE"; + + auto patch_folder = game_folder; + patch_folder += "-patch"; + + if (std::filesystem::exists(update_folder / "sce_sys" / sceItem)) { + outputPath = update_folder / "sce_sys" / sceItem; + } else if (std::filesystem::exists(patch_folder / "sce_sys" / sceItem)) { + outputPath = patch_folder / "sce_sys" / sceItem; + } else { + outputPath = game_folder / "sce_sys" / sceItem; + } + + return outputPath; +} + +void SetGameIcons(std::vector& gameIcons) { + ImGuiStyle& style = ImGui::GetStyle(); + const float maxAvailableWidth = ImGui::GetContentRegionAvail().x; + const float itemSpacing = style.ItemSpacing.x; // already scaled + const float padding = 10.0f * uiScale; + float rowContentWidth = gameImageSize * uiScale + itemSpacing; + + for (int i = 0; i < gameIcons.size(); i++) { + ImGui::BeginGroup(); + std::string ButtonName = "Button" + std::to_string(i); + const char* ButtonNameChar = ButtonName.c_str(); + + bool buttonFocused = (ImGui::GetID(ButtonNameChar) == ImGui::GetFocusID()); + if (buttonFocused) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); + } + + ImTextureID id = gameIcons[i].textureId; + if (id != nullptr) { + if (ImGui::ImageButton(ButtonNameChar, id, + ImVec2(gameImageSize * uiScale, gameImageSize * uiScale))) { + done = true; + runEbootPath = gameIcons[i].ebootPath; + } + } + + if (buttonFocused) { + ImGui::PopStyleColor(); + } + + // Scroll to item only when newly-focused + if (ImGui::IsItemFocused() && !gameIcons[i].focusState) { + ImGui::SetScrollHereY(0.5f); + } + + if (ImGui::IsWindowFocused()) { + gameIcons[i].focusState = ImGui::IsItemFocused(); + } + + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale); + ImGui::TextWrapped("%s", gameIcons[i].title.c_str()); + ImGui::PopTextWrapPos(); + ImGui::EndGroup(); + + // Use same line if content fits horizontally, move to next line if not + rowContentWidth += (gameImageSize * uiScale + itemSpacing * 2 + padding); + if (rowContentWidth < maxAvailableWidth) { + ImGui::SameLine(0.0f, padding); + } else { + ImGui::Dummy(ImVec2(0.0f, padding)); + rowContentWidth = gameImageSize * uiScale + itemSpacing; + } + } +} + +} // namespace + +SDL_Texture* LoadSdlTextureData(std::vector data) { + int image_width = 0; + int image_height = 0; + int channels = 4; + unsigned char* image_data = stbi_load_from_memory( + (const unsigned char*)data.data(), (int)data.size(), &image_width, &image_height, NULL, 4); + if (image_data == nullptr) { + LOG_ERROR(ImGui, "Failed to load image: {}", stbi_failure_reason()); + } + + SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32, + (void*)image_data, channels * image_width); + if (surface == nullptr) { + LOG_ERROR(ImGui, "Unable to create SDL surface: {}", SDL_GetError()); + } + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + if (texture == nullptr) { + LOG_ERROR(ImGui, "Unable to create SDL texture: {}", SDL_GetError()); + } + + SDL_DestroySurface(surface); + stbi_image_free(image_data); + + return texture; +} + +SDL_Texture* LoadSdlTextureDataFromFile(std::filesystem::path filePath) { + std::ifstream file(filePath, std::ios::binary); + std::vector data = + std::vector(std::istreambuf_iterator(file), std::istreambuf_iterator()); + return LoadSdlTextureData(data); +} + +void GetGameIconInfo(std::vector& icons) { + icons.clear(); + + for (const auto& installLoc : EmulatorSettings.GetAllGameInstallDirs()) { + if (installLoc.enabled && std::filesystem::exists(installLoc.path)) { + for (const auto& entry : std::filesystem::directory_iterator(installLoc.path)) { + if (entry.path().filename().string().ends_with("-UPDATE") || + entry.path().filename().string().ends_with("-patch") || !entry.is_directory()) { + continue; + } + + IconInfo icon; + PSF psf; + const std::string sfoFileName = "param.sfo"; + std::filesystem::path sfoPath = UpdateChecker(sfoFileName, entry.path()); + + if (std::filesystem::exists(sfoPath) && psf.Open(sfoPath)) { + if (const auto title = psf.GetString("TITLE"); title.has_value()) { + icon.title = *title; + } + + if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) { + icon.serial = *title_id; + } + } else { + continue; + } + + const std::string iconFileName = "icon0.png"; + std::filesystem::path iconPath = UpdateChecker(iconFileName, entry.path()); + + SDL_Texture* texture = LoadSdlTextureDataFromFile(iconPath); + icon.textureId = ImTextureID(texture); + + icon.ebootPath = entry.path() / "eboot.bin"; + icon.focusState = false; + icons.push_back(icon); + } + } + } + + std::sort(icons.begin(), icons.end(), [](const IconInfo& a, const IconInfo& b) { + return a.title < b.title; // Alphabetical order + }); +} void Launch(char* executableName) { if (!SDL_Init(SDL_INIT_VIDEO)) { @@ -42,15 +197,12 @@ void Launch(char* executableName) { if (!SDL_Init(SDL_INIT_GAMEPAD)) { LOG_ERROR(ImGui, "SDL_INIT_GAMEPAD Error: {}", SDL_GetError()); - SDL_Quit(); - return; } SDL_Window* window = SDL_CreateWindow("shadPS4 Big Picture Mode", 1280, 720, SDL_WINDOW_FULLSCREEN); renderer = SDL_CreateRenderer(window, nullptr); - // Check if window creation failed if (window == nullptr) { LOG_ERROR(ImGui, "SDL Window Creation Error: {}", SDL_GetError()); SDL_DestroyRenderer(renderer); @@ -66,38 +218,21 @@ void Launch(char* executableName) { io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigNavCursorVisibleAlways = true; - ImFontConfig font_cfg; - font_cfg.OversampleH = 2; - font_cfg.OversampleV = 1; - ImFont* myFont = ImGui::FontStack::AddPrimaryUiFont( - io.Fonts, 32.0f, EmulatorSettings.GetConsoleLanguage(), font_cfg, true); - io.FontDefault = myFont; + ImFontConfig cfgBase; + cfgBase.OversampleH = 2; + cfgBase.OversampleV = 1; + io.FontDefault = ImGui::FontStack::AddPrimaryUiFont( + io.Fonts, 64.0f, EmulatorSettings.GetConsoleLanguage(), cfgBase, true); + io.FontGlobalScale = 0.5f; io.Fonts->Build(); - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4* colors = style.Colors; - - colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f); // black - colors[ImGuiCol_Header] = ImVec4(0.20f, 0.40f, 0.70f, 1.00f); // blue - colors[ImGuiCol_HeaderHovered] = ImVec4(0.25f, 0.50f, 0.85f, 1.00f); // lighter blue - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); // another light blue - colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); // another light blue - - style.WindowRounding = 0.0f; - style.FrameRounding = 5.0f * uiScale; - style.ItemSpacing = ImVec2(10.0f * uiScale, 10.0f * uiScale); - style.FramePadding = ImVec2(10.0f * uiScale, 10.0f * uiScale); - style.FrameBorderSize = 2.5f * uiScale; - style.WindowBorderSize = 0.0f; - style.WindowPadding = ImVec2(20.0f * uiScale, 20.0f * uiScale); - style.GrabMinSize = 20.0f * uiScale; - ImGui_ImplSDL3_InitForSDLRenderer(window, renderer); ImGui_ImplSDLRenderer3_Init(renderer); - GetGameInfo(gameVec, false); + GetGameIconInfo(gameIcons); uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); + ImGuiEmuSettings::SettingsWindow settingsWindow(false); while (!done) { SDL_Event event; @@ -111,7 +246,24 @@ void Launch(char* executableName) { ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); - ImGui::PushFont(myFont); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.06f, 0.06f, 0.06f, 1.00f)); // black + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.40f, 0.70f, 1.00f)); // blue + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + ImVec4(0.25f, 0.50f, 0.85f, 1.00f)); // lighter blue + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, + ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue + ImGui::PushStyleColor(ImGuiCol_SliderGrab, + ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.0f * uiScale); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f * uiScale, 10.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f * uiScale, 10.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.5f * uiScale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f * uiScale, 20.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 20.0f * uiScale); ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); @@ -139,7 +291,7 @@ void Launch(char* executableName) { ImGui::SetKeyboardFocusHere(); } - SetGameIcons(gameVec); + SetGameIcons(gameIcons); ImGui::EndChild(); ImGui::Separator(); @@ -200,17 +352,18 @@ void Launch(char* executableName) { if (showSettings) { EmulatorSettings.SetBigPictureScale(static_cast(uiScale * 1000)); EmulatorSettings.Save(); - DrawSettings(&showSettings); + settingsWindow.DrawSettings(&showSettings); // update when settings dialog closed if (!showSettings) { uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); sliderScale = uiScale; - GetGameInfo(gameVec, false); + GetGameIconInfo(gameIcons); } } - ImGui::PopFont(); + ImGui::PopStyleVar(8); + ImGui::PopStyleColor(5); ImGui::End(); ImGui::Render(); SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255); @@ -236,163 +389,4 @@ void Launch(char* executableName) { } } -void SetGameIcons(std::vector& games) { - ImGuiStyle& style = ImGui::GetStyle(); - const float maxAvailableWidth = ImGui::GetContentRegionAvail().x; - const float itemSpacing = style.ItemSpacing.x; // already scaled - const float padding = 10.0f * uiScale; - float rowContentWidth = gameImageSize * uiScale + itemSpacing; - - for (int i = 0; i < games.size(); i++) { - ImGui::BeginGroup(); - std::string ButtonName = "Button" + std::to_string(i); - const char* ButtonNameChar = ButtonName.c_str(); - - bool isNextItemFocused = (ImGui::GetID(ButtonNameChar) == ImGui::GetFocusID()); - bool popColor = false; - if (isNextItemFocused) { - ImGui::PushStyleColor(ImGuiCol_Button, - ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); - popColor = true; - } - - if (ImGui::ImageButton(ButtonNameChar, (ImTextureID)games[i].iconTexture, - ImVec2(gameImageSize * uiScale, gameImageSize * uiScale))) { - done = true; - runEbootPath = games[i].ebootPath; - } - - if (popColor) { - ImGui::PopStyleColor(); - } - - // Scroll to item only when newly-focused - if (ImGui::IsItemFocused() && !games[i].focusState) { - ImGui::SetScrollHereY(0.5f); - } - - if (ImGui::IsWindowFocused()) - games[i].focusState = ImGui::IsItemFocused(); - - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale); - ImGui::TextWrapped("%s", games[i].title.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndGroup(); - - // Use same line if content fits horizontally, move to next line if not - rowContentWidth += (gameImageSize * uiScale + itemSpacing * 2 + padding); - if (rowContentWidth < maxAvailableWidth) { - ImGui::SameLine(0.0f, padding); - } else { - ImGui::Dummy(ImVec2(0.0f, padding)); - rowContentWidth = gameImageSize * uiScale + itemSpacing; - } - } -} - -std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem::path game_folder) { - std::filesystem::path outputPath; - auto update_folder = game_folder; - update_folder += "-UPDATE"; - - auto patch_folder = game_folder; - patch_folder += "-patch"; - - if (std::filesystem::exists(update_folder / "sce_sys" / sceItem)) { - outputPath = update_folder / "sce_sys" / sceItem; - } else if (std::filesystem::exists(patch_folder / "sce_sys" / sceItem)) { - outputPath = patch_folder / "sce_sys" / sceItem; - } else { - outputPath = game_folder / "sce_sys" / sceItem; - } - - return outputPath; -} - -void GetGameInfo(std::vector& games, bool AddGlobalSettings, SDL_Texture* texture) { - games.clear(); - if (AddGlobalSettings) { - Game global; - global.title = "Global"; - global.iconTexture = texture; - global.focusState = false; - games.push_back(global); - } - - for (const auto& installLoc : EmulatorSettings.GetAllGameInstallDirs()) { - if (installLoc.enabled && std::filesystem::exists(installLoc.path)) { - for (const auto& entry : std::filesystem::directory_iterator(installLoc.path)) { - if (entry.path().filename().string().ends_with("-UPDATE") || - entry.path().filename().string().ends_with("-patch") || !entry.is_directory()) { - continue; - } - - Game game; - PSF psf; - const std::string sfoFileName = "param.sfo"; - std::filesystem::path sfoPath = UpdateChecker(sfoFileName, entry.path()); - - if (psf.Open(sfoPath)) { - if (const auto title = psf.GetString("TITLE"); title.has_value()) { - game.title = *title; - } - - if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) { - game.serial = *title_id; - } - } else { - continue; - } - - const std::string iconFileName = "icon0.png"; - std::filesystem::path iconPath = UpdateChecker(iconFileName, entry.path()); - LoadTextureDataFromFile(iconPath, game.iconTexture, renderer); - - game.ebootPath = entry.path() / "eboot.bin"; - game.focusState = false; - games.push_back(game); - } - } - } - - // Keep global settings at the start if it's added - auto start = AddGlobalSettings ? games.begin() + 1 : (games.begin()); - std::sort(start, games.end(), [](const Game& a, const Game& b) { - return a.title < b.title; // Alphabetical order - }); -} - -void LoadTextureDataFromFile(std::filesystem::path filePath, SDL_Texture*& texture, - SDL_Renderer* m_renderer) { - std::ifstream file(filePath, std::ios::binary); - std::vector data = - std::vector(std::istreambuf_iterator(file), std::istreambuf_iterator()); - LoadTextureData(data, texture, m_renderer); -} - -void LoadTextureData(std::vector data, SDL_Texture*& texture, SDL_Renderer* m_renderer) { - int image_width = 0; - int image_height = 0; - int channels = 4; - unsigned char* image_data = stbi_load_from_memory( - (const unsigned char*)data.data(), (int)data.size(), &image_width, &image_height, NULL, 4); - if (image_data == nullptr) { - LOG_ERROR(ImGui, "Failed to load image: {}", stbi_failure_reason()); - } - - SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32, - (void*)image_data, channels * image_width); - if (surface == nullptr) { - LOG_ERROR(ImGui, "Unable to create SDL surface: {}", SDL_GetError()); - } - - texture = SDL_CreateTextureFromSurface(m_renderer, surface); - if (texture == nullptr) { - LOG_ERROR(ImGui, "Unable to create SDL texture: {}", SDL_GetError()); - } - - SDL_DestroySurface(surface); - stbi_image_free(image_data); -} - } // namespace BigPictureMode diff --git a/src/imgui/big_picture/big_picture.h b/src/imgui/big_picture/big_picture.h new file mode 100644 index 000000000..ae2730ccd --- /dev/null +++ b/src/imgui/big_picture/big_picture.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/types.h" + +namespace BigPictureMode { + +struct IconInfo { + ImTextureID textureId; + std::filesystem::path ebootPath; + std::string title; + std::string serial; + bool focusState; +}; + +void Launch(char* executableName); +void GetGameIconInfo(std::vector& icons); +SDL_Texture* LoadSdlTextureData(std::vector data); +SDL_Texture* LoadSdlTextureDataFromFile(std::filesystem::path filePath); + +} // namespace BigPictureMode diff --git a/src/imgui/renderer/imgui_impl_sdl3_bpm.cpp b/src/imgui/big_picture/imgui_impl_sdl3_big_picture.cpp similarity index 99% rename from src/imgui/renderer/imgui_impl_sdl3_bpm.cpp rename to src/imgui/big_picture/imgui_impl_sdl3_big_picture.cpp index 6fa8b3936..51de711c8 100644 --- a/src/imgui/renderer/imgui_impl_sdl3_bpm.cpp +++ b/src/imgui/big_picture/imgui_impl_sdl3_big_picture.cpp @@ -5,7 +5,7 @@ #include "imgui.h" #ifndef IMGUI_DISABLE -#include "imgui_impl_sdl3_bpm.h" +#include "imgui_impl_sdl3_big_picture.h" // Clang warnings with -Weverything #if defined(__clang__) diff --git a/src/imgui/renderer/imgui_impl_sdl3_bpm.h b/src/imgui/big_picture/imgui_impl_sdl3_big_picture.h similarity index 100% rename from src/imgui/renderer/imgui_impl_sdl3_bpm.h rename to src/imgui/big_picture/imgui_impl_sdl3_big_picture.h diff --git a/src/imgui/renderer/imgui_impl_sdlrenderer3.cpp b/src/imgui/big_picture/imgui_impl_sdlrenderer3.cpp similarity index 100% rename from src/imgui/renderer/imgui_impl_sdlrenderer3.cpp rename to src/imgui/big_picture/imgui_impl_sdlrenderer3.cpp diff --git a/src/imgui/renderer/imgui_impl_sdlrenderer3.h b/src/imgui/big_picture/imgui_impl_sdlrenderer3.h similarity index 100% rename from src/imgui/renderer/imgui_impl_sdlrenderer3.h rename to src/imgui/big_picture/imgui_impl_sdlrenderer3.h diff --git a/src/imgui/big_picture/settings_dialog_imgui.cpp b/src/imgui/big_picture/settings_dialog_imgui.cpp new file mode 100644 index 000000000..838f6c7cd --- /dev/null +++ b/src/imgui/big_picture/settings_dialog_imgui.cpp @@ -0,0 +1,813 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "common/path_util.h" +#include "core/devtools/layer.h" +#include "imgui/imgui_std.h" +#include "settings_dialog_imgui.h" + +CMRC_DECLARE(res); + +constexpr float gameImageSize = 200.f; +constexpr float settingsIconSize = 125.f; + +namespace ImGuiEmuSettings { + +int SettingsWindow::GetComboIndex(std::string selection, std::vector options) { + for (int i = 0; i < options.size(); i++) { + if (selection == options[i]) + return i; + } + + return 0; +} + +void SettingsWindow::LoadSettings(std::string profile) { + const bool isSpecific = currentProfile != "Global"; + isSpecific ? EmulatorSettings.Load(profile) : EmulatorSettings.Load(); + + /////////// General Tab + int languageIndex = EmulatorSettings.GetConsoleLanguage(); + std::string language; + for (const auto& [key, value] : languageMap) { + if (value == languageIndex) { + language = key; + } + } + + consoleLanguageSetting = GetComboIndex(language, languageOptions); + volumeSetting = EmulatorSettings.GetVolumeSlider(); + showSplashSetting = EmulatorSettings.IsShowSplash(); + audioBackendSetting = EmulatorSettings.GetAudioBackend(); + + /////////// Graphics Tab + fullscreenModeSetting = + GetComboIndex(EmulatorSettings.GetFullScreenMode(), fullscreenModeOptions); + presentModeSetting = GetComboIndex(EmulatorSettings.GetPresentMode(), presentModeOptions); + windowHeightSetting = EmulatorSettings.GetWindowHeight(); + windowWidthSetting = EmulatorSettings.GetWindowWidth(); + hdrAllowedSetting = EmulatorSettings.IsHdrAllowed(); + fsrEnabledSetting = EmulatorSettings.IsFsrEnabled(); + rcasEnabledSetting = EmulatorSettings.IsRcasEnabled(); + rcasAttenuationSetting = static_cast(EmulatorSettings.GetRcasAttenuation() * 0.001f); + + /////////// Input Tab + motionControlsSetting = EmulatorSettings.IsMotionControlsEnabled(); + backgroundControllerSetting = EmulatorSettings.IsBackgroundControllerInput(); + cursorStateSetting = EmulatorSettings.GetCursorState(); + cursorTimeoutSetting = EmulatorSettings.GetCursorHideTimeout(); + + /////////// Trophy Tab + trophyPopupDisabledSetting = EmulatorSettings.IsTrophyPopupDisabled(); + trophySideSetting = + GetComboIndex(EmulatorSettings.GetTrophyNotificationSide(), trophySideOptions); + trophyDurationSetting = static_cast(EmulatorSettings.GetTrophyNotificationDuration()); + + /////////// Log Tab + logEnableSetting = EmulatorSettings.IsLogEnable(); + logSeparateSetting = EmulatorSettings.IsLogSeparate(); + logSyncSetting = EmulatorSettings.IsLogSync(); + + /////////// Experimental Tab + if (isSpecific) { + readbacksModeSetting = EmulatorSettings.GetReadbacksMode(); + readbackLinearImagesSetting = EmulatorSettings.IsReadbackLinearImagesEnabled(); + directMemoryAccessSetting = EmulatorSettings.IsDirectMemoryAccessEnabled(); + devkitConsoleSetting = EmulatorSettings.IsDevKit(); + neoModeSetting = EmulatorSettings.IsNeo(); + shadnetEnabledSetting = EmulatorSettings.IsShadNetEnabled(); + connectedNetworkSetting = EmulatorSettings.IsConnectedToNetwork(); + pipelineCacheEnabledSetting = EmulatorSettings.IsPipelineCacheEnabled(); + pipelineCacheArchiveSetting = EmulatorSettings.IsPipelineCacheArchived(); + extraDmemSetting = EmulatorSettings.GetExtraDmemInMBytes(); + vblankFrequencySetting = EmulatorSettings.GetVblankFrequency(); + } +} + +void SettingsWindow::SaveSettings(std::string profile) { + const bool isSpecific = currentProfile != "Global"; + + /////////// General Tab + EmulatorSettings.SetConsoleLanguage(languageMap.at(languageOptions.at(consoleLanguageSetting)), + isSpecific); + EmulatorSettings.SetVolumeSlider(volumeSetting, isSpecific); + EmulatorSettings.SetShowSplash(showSplashSetting, isSpecific); + EmulatorSettings.SetAudioBackend(audioBackendSetting, isSpecific); + + /////////// Graphics Tab + bool isFullscreen = fullscreenModeSetting != 0; + EmulatorSettings.SetFullScreen(isFullscreen); + EmulatorSettings.SetFullScreenMode(fullscreenModeOptions.at(fullscreenModeSetting), isSpecific); + EmulatorSettings.SetPresentMode(presentModeOptions.at(presentModeSetting), isSpecific); + EmulatorSettings.SetWindowHeight(windowHeightSetting, isSpecific); + EmulatorSettings.SetWindowWidth(windowWidthSetting, isSpecific); + EmulatorSettings.SetHdrAllowed(hdrAllowedSetting, isSpecific); + EmulatorSettings.SetFsrEnabled(fsrEnabledSetting, isSpecific); + EmulatorSettings.SetRcasEnabled(rcasEnabledSetting, isSpecific); + EmulatorSettings.SetRcasAttenuation(static_cast(rcasAttenuationSetting * 1000), + isSpecific); + + /////////// Input Tab + EmulatorSettings.SetMotionControlsEnabled(motionControlsSetting, isSpecific); + EmulatorSettings.SetBackgroundControllerInput(backgroundControllerSetting, isSpecific); + EmulatorSettings.SetCursorState(cursorStateSetting, isSpecific); + EmulatorSettings.SetCursorHideTimeout(cursorTimeoutSetting, isSpecific); + + /////////// Trophy Tab + EmulatorSettings.SetTrophyPopupDisabled(trophyPopupDisabledSetting, isSpecific); + EmulatorSettings.SetTrophyNotificationSide(trophySideOptions.at(trophySideSetting), isSpecific); + EmulatorSettings.SetTrophyNotificationDuration(static_cast(trophyDurationSetting)); + + /////////// Log Tab + EmulatorSettings.SetLogEnable(logEnableSetting, isSpecific); + EmulatorSettings.SetLogSeparate(logSeparateSetting, isSpecific); + EmulatorSettings.SetLogSync(logSyncSetting, isSpecific); + + /////////// Experimental Tab + if (isSpecific) { + EmulatorSettings.SetReadbacksMode(readbacksModeSetting, true); + EmulatorSettings.SetReadbackLinearImagesEnabled(readbackLinearImagesSetting, true); + EmulatorSettings.SetDirectMemoryAccessEnabled(directMemoryAccessSetting, true); + EmulatorSettings.SetDevKit(devkitConsoleSetting, true); + EmulatorSettings.SetNeo(neoModeSetting, true); + EmulatorSettings.SetShadNetEnabled(shadnetEnabledSetting, true); + EmulatorSettings.SetConnectedToNetwork(connectedNetworkSetting, true); + EmulatorSettings.SetPipelineCacheEnabled(pipelineCacheEnabledSetting, true); + EmulatorSettings.SetPipelineCacheArchived(pipelineCacheArchiveSetting, true); + EmulatorSettings.SetExtraDmemInMBytes(extraDmemSetting, true); + EmulatorSettings.SetVblankFrequency(vblankFrequencySetting, true); + } + + isSpecific ? EmulatorSettings.Save(profile) : EmulatorSettings.Save(); +} + +void SettingsWindow::SaveInstallDirs() { + std::string profile; + const bool isGlobal = currentProfile == "Global"; + if (!isGlobal) { + profile = currentProfile.substr(0, 9); + EmulatorSettings.Load(); + } + + EmulatorSettings.SetAllGameInstallDirs(m_GameInstallDirs); + EmulatorSettings.Save(); + + if (!isGlobal) { + EmulatorSettings.Load(profile); + } + + if (!isGameRunning) { + GetProfileInfo(); + } +} + +void SettingsWindow::GetProfileInfo() { + GetGameIconInfo(profileIcons); + + BigPictureMode::IconInfo global; + global.title = "Global"; + profileIcons.emplace(profileIcons.begin(), global); +} + +SettingsWindow::SettingsWindow(bool gameRunning) : isGameRunning(gameRunning) { + auto resource = cmrc::res::get_filesystem(); + auto loadTexture = [&](const std::string& resourcePath, + std::variant& texture) { + auto file = resource.open(resourcePath); + std::vector texData = std::vector(file.begin(), file.end()); + gameRunning ? texture = ImGui::RefCountedTexture::DecodePngTexture(texData) + : texture = BigPictureMode::LoadSdlTextureData(texData); + }; + + loadTexture("src/images/big_picture/settings.png", generalTexture); + loadTexture("src/images/big_picture/experimental.png", experimentalTexture); + loadTexture("src/images/big_picture/graphics.png", graphicsTexture); + loadTexture("src/images/big_picture/controller.png", inputTexture); + loadTexture("src/images/big_picture/trophy.png", trophyTexture); + loadTexture("src/images/big_picture/log.png", logTexture); + loadTexture("src/images/big_picture/folder.png", foldersTexture); + loadTexture("src/images/big_picture/profiles.png", profilesTexture); + + auto languageKeys = std::views::keys(languageMap); + languageOptions.assign(languageKeys.begin(), languageKeys.end()); + + currentProfile = "Global"; + m_GameInstallDirs = EmulatorSettings.GetAllGameInstallDirs(); + currentCategory = isGameRunning ? SettingsCategory::General : SettingsCategory::Profiles; + uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); + + bool customConfigFound = false; + if (isGameRunning) { + runningGameSerial = std::string(Common::ElfInfo::Instance().GameSerial()); + std::filesystem::path customConfigFile = + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (runningGameSerial + ".json"); + + if (std::filesystem::exists(customConfigFile)) { + customConfigFound = true; + currentProfile = + runningGameSerial + " - " + std::string(Common::ElfInfo::Instance().Title()); + } + } else { + GetProfileInfo(); + } + + customConfigFound ? LoadSettings(runningGameSerial) : LoadSettings("Global"); +} + +void SettingsWindow::DeInit() { + EmulatorSettings.Load(); + EmulatorSettings.SetBigPictureScale(static_cast(uiScale * 1000)); + EmulatorSettings.Save(); + + if (isGameRunning && !runningGameSerial.empty()) { + EmulatorSettings.Load(runningGameSerial); + } +} +void SettingsWindow::DrawSettings(bool* open) { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.06f, 0.06f, 0.06f, 1.00f)); // black + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.40f, 0.70f, 1.00f)); // blue + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + ImVec4(0.25f, 0.50f, 0.85f, 1.00f)); // lighter blue + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, + ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue + ImGui::PushStyleColor(ImGuiCol_SliderGrab, + ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.0f * uiScale); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f * uiScale, 10.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f * uiScale, 10.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.5f * uiScale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f * uiScale, 20.0f * uiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 20.0f * uiScale); + + SetupWindow(); + DrawCategoryTabs(); + DrawMainContent(open); + + ImGui::PopStyleVar(8); + ImGui::PopStyleColor(5); + + ImGui::End(); +} + +void SettingsWindow::SetupWindow() { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + + ImGui::Begin("Settings", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::DrawPrettyBackground(); + ImGui::SetWindowFontScale(uiScale); + + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + } + + SettingsCategory firstCategory = + isGameRunning ? SettingsCategory::General : SettingsCategory::Profiles; + SettingsCategory lastCategory = + currentProfile != "Global" ? SettingsCategory::Experimental : SettingsCategory::Log; + + // Navigate categories with Tab / R1 / L1 + if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1) || ImGui::IsKeyPressed(ImGuiKey_Tab)) { + int currentIndex = static_cast(currentCategory); + currentCategory == lastCategory + ? currentCategory = static_cast(firstCategory) + : currentCategory = static_cast(currentIndex + 1); + } + + if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) { + int currentIndex = static_cast(currentCategory); + currentIndex == static_cast(firstCategory) + ? currentCategory = lastCategory + : currentCategory = static_cast(currentIndex - 1); + } +} + +void SettingsWindow::DrawCategoryTabs() { + ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray + ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor); + + float vertSize = (settingsIconSize * uiScale + ImGui::CalcTextSize("Profiles").y) + + ImGui::GetStyle().FramePadding.y * 4.f + 20.0 * uiScale; + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + ImGui::BeginChild("Categories", ImVec2(0, vertSize), true, + child_flags | ImGuiWindowFlags_HorizontalScrollbar | + ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30.0f * uiScale, 0.0f)); + + // Must add categories in enum order for L1/R1 to work correctly + if (!isGameRunning) { + AddCategory("Profiles", profilesTexture, SettingsCategory::Profiles); + } + + AddCategory("General", generalTexture, SettingsCategory::General); + AddCategory("Graphics", graphicsTexture, SettingsCategory::Graphics); + AddCategory("Input", inputTexture, SettingsCategory::Input); + AddCategory("Trophy", trophyTexture, SettingsCategory::Trophy); + AddCategory("Game Folders", foldersTexture, SettingsCategory::Folders); + AddCategory("Log", logTexture, SettingsCategory::Log); + + if (currentProfile != "Global") { + AddCategory("Experimental", experimentalTexture, SettingsCategory::Experimental); + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::EndChild(); +} + +void SettingsWindow::AddCategory(std::string name, + std::variant texture, + SettingsCategory category) { + ImGui::SameLine(); + ImGui::BeginGroup(); + + // make button appear hovered as long as category is selected, otherwise dull its hovered color + currentCategory == category + ? ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]) + : ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.235f, 0.392f, 0.624f, 1.00f)); + + ImTextureID id = nullptr; + if (std::holds_alternative(texture)) { + id = ImTextureID(std::get(texture)); + } else if (std::holds_alternative(texture)) { + id = std::get(texture).GetTexture().im_id; + } + + if (id != nullptr) { + if (ImGui::ImageButton(name.c_str(), id, + ImVec2(settingsIconSize * uiScale, settingsIconSize * uiScale))) { + currentCategory = category; + } + } + + ImGui::PopStyleColor(); + + ImGui::SetCursorPosX( + (ImGui::GetCursorPosX() + + (settingsIconSize * uiScale - ImGui::CalcTextSize(name.c_str()).x) * 0.5f) + + ImGui::GetStyle().FramePadding.x); + ImGui::Text("%s", name.c_str()); + ImGui::EndGroup(); +} + +void SettingsWindow::DrawMainContent(bool* open) { + ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray + ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor); + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + + std::string centeredText; + currentCategory == SettingsCategory::Folders + ? centeredText = "Manage shadPS4 Game Folders" + : centeredText = "Selected Profile: " + currentProfile; + + ImGui::Separator(); + Overlay::TextCentered(centeredText.c_str()); + ImGui::Separator(); + + if (currentCategory == SettingsCategory::Profiles) { + DrawProfileSelector(); + } else if (currentCategory == SettingsCategory::Folders) { + DrawGameFolderManager(); + } else { + DrawSettingsTable(currentCategory); + } + + ImGui::PopStyleColor(); + + ImGui::Separator(); + ImGui::SetNextItemWidth(300.0f * uiScale); + static float sliderScale2 = 1.0f; + if (ImGui::IsWindowAppearing()) { + sliderScale2 = uiScale; + } + + ImGui::SliderFloat("UI Scale", &sliderScale2, 0.25f, 3.0f); + // Only update when user is not interacting with slider + if (ImGui::IsItemDeactivatedAfterEdit()) { + uiScale = sliderScale2; + } + ImGui::SameLine(); + + // Align buttons right + float buttonsWidth = ImGui::CalcTextSize("Save").x + ImGui::CalcTextSize("Cancel").x + + ImGui::CalcTextSize("Apply").x + ImGui::GetStyle().FramePadding.x * 6.0f + + ImGui::GetStyle().ItemSpacing.x * 2; + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - buttonsWidth); + + if (ImGui::Button("Save")) { + closeOnSave = true; + ImGui::OpenPopup("Save Confirmation"); + } + + ImGui::SameLine(); + if (ImGui::Button("Apply")) { + ImGui::OpenPopup("Save Confirmation"); + } + + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal("Save Confirmation", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("%s", ("Profile Saved:\n" + currentProfile).c_str()); + ImGui::Separator(); + + if (ImGui::Button("OK", ImVec2(250 * uiScale, 0))) { + std::string profile = currentProfile; + if (currentProfile != "Global") { + profile = currentProfile.substr(0, 9); + } + + SaveSettings(profile); + if (closeOnSave) { + DeInit(); + *open = false; + closeOnSave = false; + ImGui::CloseCurrentPopup(); + } else { + ImGui::CloseCurrentPopup(); + } + } + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + DeInit(); + *open = false; + } +} + +void SettingsWindow::DrawProfileSelector() { + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray + ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); + + ImGui::BeginChild("Profile Selection", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true, + child_flags); + ImGui::PopStyleColor(); + + if (ImGui::BeginTable("ProfilesTable", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("CreateDeleteButton", ImGuiTableColumnFlags_WidthFixed, + 400.0f * uiScale); + ImGui::TableSetupColumn("Profile"); + + for (int i = 0; i < profileIcons.size(); i++) { + const std::filesystem::path customConfigFile = + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (profileIcons[i].serial + ".json"); + const bool gameConfigExists = std::filesystem::exists(customConfigFile); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + std::string deleteButtonLabel = "Delete game-specific config##" + std::to_string(i); + + if (gameConfigExists) { + // Different shades of red depending on button state + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.0f, 0.0f, 1.0f)); + + if (ImGui::Button(deleteButtonLabel.c_str(), + ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) { + deleteProfileIndex = i; + } + + ImGui::PopStyleColor(3); + } + + ImGui::TableNextColumn(); + std::string profileLabel = + i == 0 ? "Global" : profileIcons[i].serial + " - " + profileIcons[i].title; + if (ImGui::Button(profileLabel.c_str(), + ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) { + + currentProfile = profileLabel; + if (currentProfile == "Global") { + LoadSettings("Global"); + } else { + LoadSettings(profileIcons[i].serial); + if (!gameConfigExists) { + SaveSettings(profileIcons[i].serial); + } + } + } + } + + ImGui::EndTable(); + } + + if (deleteProfileIndex != -1) { + ImGui::OpenPopup("Confirm Delete"); + } + + ImGui::PopStyleVar(3); + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal("Confirm Delete", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + const std::string title = profileIcons[deleteProfileIndex].title; + const std::string message = "Delete game-specific config file for " + title + "?"; + const std::filesystem::path path = + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (profileIcons[deleteProfileIndex].serial + ".json"); + + ImGui::Text("%s", message.c_str()); + ImGui::Separator(); + + if (ImGui::Button("OK", ImVec2(120 * uiScale, 0))) { + try { + std::filesystem::remove(path); + } catch (const std::exception& e) { + LOG_ERROR(ImGui, "Could not delete config file {}: {}", path.string(), e.what()); + } + + deleteProfileIndex = -1; + currentProfile = "Global"; + LoadSettings("Global"); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + + if (ImGui::Button("Cancel", ImVec2(120 * uiScale, 0))) { + deleteProfileIndex = -1; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + ImGui::EndChild(); +} + +void SettingsWindow::DrawGameFolderManager() { + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true, + child_flags); + + if (ImGui::Button("Add Folder", ImVec2(400.f * uiScale, 0))) { + ImGuiFileDialog::Instance()->OpenDialog("OpenFolder", "Add shadPS4 game folder", nullptr, + ".", 1, nullptr, + ImGuiFileDialogFlags_DisableCreateDirectoryButton | + ImGuiFileDialogFlags_DontShowHiddenFiles); + + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + } + + if (ImGuiFileDialog::Instance()->Display("OpenFolder", child_flags | ImGuiWindowFlags_NoMove)) { + if (ImGuiFileDialog::Instance()->IsOk()) { + GameInstallDir dir; + dir.path = ImGuiFileDialog::Instance()->GetCurrentPath(); + dir.enabled = true; + +#ifdef WIN32 + // replace \ with / on windows + std::string pathString = dir.path.string(); + size_t pos = 0; + while ((pos = pathString.find("\\", pos)) != std::string::npos) { + pathString.replace(pos, 1, "/"); + pos += 2; + } + + dir.path = pathString; +#endif + + m_GameInstallDirs.push_back(dir); + SaveInstallDirs(); + } + + ImGuiFileDialog::Instance()->Close(); + } + + ImGui::BeginChild("Game Folder List", ImVec2(0, 0), true, child_flags); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 5.0f * uiScale)); + if (ImGui::BeginTable("FoldersTable", 3)) { + ImGui::TableSetupColumn("FolderButton", ImGuiTableColumnFlags_WidthFixed, 300.0f * uiScale); + ImGui::TableSetupColumn("FolderEnabled", ImGuiTableColumnFlags_WidthFixed, 0.0f); + ImGui::TableSetupColumn("FolderPath"); + + for (int i = 0; i < m_GameInstallDirs.size(); i++) { + std::string buttonLabel = "Remove Folder##" + std::to_string(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button(buttonLabel.c_str(), ImVec2(280 * uiScale, 0))) { + m_GameInstallDirs.erase(m_GameInstallDirs.begin() + i); + SaveInstallDirs(); + } + + ImGui::TableNextColumn(); + std::string checkboxLabel = "##EnableFolder" + std::to_string(i); + bool previousState = m_GameInstallDirs[i].enabled; + ImGui::Checkbox(checkboxLabel.c_str(), &m_GameInstallDirs[i].enabled); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(5.0 * uiScale, 0)); + + if (m_GameInstallDirs[i].enabled != previousState) { + SaveInstallDirs(); + } + + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", m_GameInstallDirs[i].path.string().c_str()); + } + + ImGui::EndTable(); + } + + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::EndChild(); +} + +void SettingsWindow::DrawSettingsTable(SettingsCategory category) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f * uiScale, 10.0f * uiScale)); + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + + ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true, + child_flags); + + if (category == SettingsCategory::General) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCombo("Console Language", consoleLanguageSetting, languageOptions); + AddSettingSliderInt("Volume", volumeSetting, 0, 500); + AddSettingCheckbox("Show Splash Screen When Launching Game", showSplashSetting); + AddSettingCombo("Audio Backend", audioBackendSetting, audioBackendOptions); + + ImGui::EndTable(); + } + } else if (category == SettingsCategory::Graphics) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCombo("Display Mode", fullscreenModeSetting, fullscreenModeOptions); + AddSettingCombo("Present Mode", presentModeSetting, presentModeOptions); + AddSettingSliderInt("Window Width", windowWidthSetting, 0, 8000); + AddSettingSliderInt("Window Height", windowHeightSetting, 0, 7000); + AddSettingCheckbox("Enable HDR", hdrAllowedSetting); + AddSettingCheckbox("Enable FSR", fsrEnabledSetting); + + if (fsrEnabledSetting) { + AddSettingCheckbox("Enable RCAS", rcasEnabledSetting); + } + + if (rcasEnabledSetting && fsrEnabledSetting) { + AddSettingSliderFloat("RCAS Attenuation", rcasAttenuationSetting, 0.0f, 3.0f, 3); + } + + ImGui::EndTable(); + } + } else if (category == SettingsCategory::Input) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCheckbox("Enable Motion Controls", motionControlsSetting); + AddSettingCheckbox("Enable Background Controller Input", backgroundControllerSetting); + AddSettingCombo("Hide Cursor", cursorStateSetting, hideCursorOptions); + + if (cursorStateSetting == 1) { + AddSettingSliderInt("Hide Cursor Idle Timeout", cursorTimeoutSetting, 1, 10); + } + + ImGui::EndTable(); + } + } else if (category == SettingsCategory::Trophy) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCheckbox("Disable Trophy Notification", trophyPopupDisabledSetting); + if (!trophyPopupDisabledSetting) { + AddSettingCombo("Trophy Notification Position", trophySideSetting, + trophySideOptions); + AddSettingSliderFloat("Trophy Notification Duration", trophyDurationSetting, 0.f, + 10.f, 1); + } + + ImGui::EndTable(); + } + } else if (category == SettingsCategory::Log) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCheckbox("Enable Logging", logEnableSetting); + if (logEnableSetting) { + AddSettingCheckbox("Separate Log Files", logSeparateSetting); + AddSettingCheckbox("Log Sync", logSyncSetting); + } + + ImGui::EndTable(); + } + } else if (category == SettingsCategory::Experimental) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingSliderInt("Additional DMem Allocation", extraDmemSetting, 0, 20000); + AddSettingSliderInt("Vblank Frequency", vblankFrequencySetting, 30, 360); + AddSettingCombo("Readbacks Mode", readbacksModeSetting, readbacksModeOptions); + AddSettingCheckbox("Enable Readback Linear Images", readbackLinearImagesSetting); + AddSettingCheckbox("Enable Direct Memory Access", directMemoryAccessSetting); + AddSettingCheckbox("Enable Devkit Console Mode", devkitConsoleSetting); + AddSettingCheckbox("Enable PS4 Neo Mode", neoModeSetting); + AddSettingCheckbox("Enable ShadNet", shadnetEnabledSetting); + AddSettingCheckbox("Set Network Connected to True", connectedNetworkSetting); + AddSettingCheckbox("Enable Shader Cache", pipelineCacheEnabledSetting); + + if (pipelineCacheEnabledSetting) { + AddSettingCheckbox("Compress Shader Cache to Zip File", + pipelineCacheArchiveSetting); + } + + ImGui::EndTable(); + } + } + + ImGui::PopStyleVar(); + ImGui::EndChild(); +} + +void SettingsWindow::AddSettingCheckbox(std::string name, bool& value) { + std::string label = "##" + name; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::TextWrapped("%s", name.c_str()); + ImGui::TableNextColumn(); + ImGui::Checkbox(label.c_str(), &value); +} + +void SettingsWindow::AddSettingSliderInt(std::string name, int& value, int min, int max) { + std::string label = "##" + name; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", name.c_str()); + + ImGui::TableNextColumn(); + ImGui::SliderInt(label.c_str(), &value, min, max); +} + +void SettingsWindow::AddSettingSliderFloat(std::string name, float& value, int min, int max, + int precision) { + std::string label = "##" + name; + std::string precisionString = "%." + std::to_string(precision) + "f"; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", name.c_str()); + + ImGui::TableNextColumn(); + ImGui::SliderFloat(label.c_str(), &value, min, max, precisionString.c_str()); +} + +void SettingsWindow::AddSettingCombo(std::string name, int& value, + std::vector options) { + std::string label = "##" + name; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", name.c_str()); + + ImGui::TableNextColumn(); + const char* combo_value = options[value].c_str(); + if (ImGui::BeginCombo(label.c_str(), combo_value)) { + for (int i = 0; i < options.size(); i++) { + const bool selected = (i == value); + if (ImGui::Selectable(options[i].c_str(), selected)) + value = i; + + // Set the initial focus when opening the combo + if (selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } +} + +} // namespace ImGuiEmuSettings diff --git a/src/imgui/big_picture/settings_dialog_imgui.h b/src/imgui/big_picture/settings_dialog_imgui.h new file mode 100644 index 000000000..3c4383694 --- /dev/null +++ b/src/imgui/big_picture/settings_dialog_imgui.h @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "big_picture.h" +#include "core/emulator_settings.h" +#include "imgui/imgui_texture.h" + +namespace ImGuiEmuSettings { + +class SettingsWindow { + +public: + SettingsWindow(bool gameRunning); + void DrawSettings(bool* open); + +private: + enum class SettingsCategory { + Profiles, + General, + Graphics, + Input, + Trophy, + Folders, + Log, + Experimental, + }; + + void SaveSettings(std::string profile); + void LoadSettings(std::string profile); + void SaveInstallDirs(); + + void SetupWindow(); + void DeInit(); + void GetProfileInfo(); + + void DrawMainContent(bool* open); + void DrawSettingsTable(SettingsCategory); + void DrawProfileSelector(); + void DrawGameFolderManager(); + void DrawCategoryTabs(); + void AddCategory(std::string name, std::variant texture, + SettingsCategory category); + + void AddSettingCheckbox(std::string name, bool& value); + void AddSettingSliderInt(std::string name, int& value, int min, int max); + void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision); + void AddSettingCombo(std::string name, int& value, std::vector options); + int GetComboIndex(std::string selection, std::vector options); + + std::vector profileIcons = {}; + std::vector m_GameInstallDirs = {}; + + float uiScale = 1.0f; + SettingsCategory currentCategory = SettingsCategory::Profiles; + std::string currentProfile = "Global"; + + std::string runningGameSerial = ""; + bool isGameRunning = false; + bool closeOnSave = false; + int deleteProfileIndex = -1; + + std::variant profilesTexture; + std::variant generalTexture; + std::variant experimentalTexture; + std::variant graphicsTexture; + std::variant inputTexture; + std::variant trophyTexture; + std::variant logTexture; + std::variant foldersTexture; + + //////////////////// options for comboboxes + const std::map languageMap = {{"Arabic", 21}, + {"Czech", 23}, + {"Danish", 14}, + {"Dutch", 6}, + {"English (United Kingdom)", 18}, + {"English (United States)", 1}, + {"Finnish", 12}, + {"French (Canada)", 22}, + {"French (France)", 2}, + {"German", 4}, + {"Greek", 25}, + {"Hungarian", 24}, + {"Indonesian", 29}, + {"Italian", 5}, + {"Japanese", 0}, + {"Korean", 9}, + {"Norwegian (Bokmaal)", 15}, + {"Polish", 16}, + {"Portuguese (Brazil)", 17}, + {"Portuguese (Portugal)", 7}, + {"Romanian", 26}, + {"Russian", 8}, + {"Simplified Chinese", 11}, + {"Spanish (Latin America)", 20}, + {"Spanish (Spain)", 3}, + {"Swedish", 13}, + {"Thai", 27}, + {"Traditional Chinese", 10}, + {"Turkish", 19}, + {"Ukrainian", 30}, + {"Vietnamese", 28}}; + std::vector languageOptions; // assigned from keys above + const std::vector fullscreenModeOptions = {"Windowed", "Fullscreen", + "Fullscreen (Borderless)"}; + const std::vector audioBackendOptions = {"SDL", "OpenAL"}; + const std::vector presentModeOptions = {"Mailbox", "Fifo", "Immediate"}; + const std::vector hideCursorOptions = {"Never", "Idle", "Always"}; + const std::vector trophySideOptions = {"left", "right", "top", "bottom"}; + const std::vector readbacksModeOptions = {"Disabled", "Relaxed", "Precise"}; + + //////////////// Setting Variables + //////////////// Note:: Use int for all comboboxes as needed by ImGui + + // General tab + int consoleLanguageSetting; + int volumeSetting; + bool showSplashSetting; + int audioBackendSetting; + + // Graphics tab + int fullscreenModeSetting; + int presentModeSetting; + int windowWidthSetting; + int windowHeightSetting; + bool hdrAllowedSetting; + bool fsrEnabledSetting; + bool rcasEnabledSetting; + float rcasAttenuationSetting; + + // Input tab + bool motionControlsSetting; + bool backgroundControllerSetting; + int cursorStateSetting; + int cursorTimeoutSetting; + + // Trophy tab + bool trophyPopupDisabledSetting; + int trophySideSetting; + float trophyDurationSetting; + + // Log tab + bool logEnableSetting; + bool logSeparateSetting; + bool logSyncSetting; + + // Experimental tab + int readbacksModeSetting; + bool readbackLinearImagesSetting; + bool directMemoryAccessSetting; + bool devkitConsoleSetting; + bool neoModeSetting; + bool shadnetEnabledSetting; + bool connectedNetworkSetting; + bool pipelineCacheEnabledSetting; + bool pipelineCacheArchiveSetting; + int extraDmemSetting; + int vblankFrequencySetting; +}; + +} // namespace ImGuiEmuSettings diff --git a/src/imgui/big_picture/settings_dialog_layer.cpp b/src/imgui/big_picture/settings_dialog_layer.cpp new file mode 100644 index 000000000..dab411871 --- /dev/null +++ b/src/imgui/big_picture/settings_dialog_layer.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "imgui/imgui_std.h" +#include "settings_dialog_layer.h" + +namespace ImGuiEmuSettings { + +static bool running = false; +static std::optional settingsLayer = std::nullopt; + +SettingsLayer::SettingsLayer() { + AddLayer(this); +} + +SettingsLayer::~SettingsLayer() {} + +void SettingsLayer::Finish() { + RemoveLayer(this); + settingsLayer.reset(); +} + +void SettingsLayer::Draw() { + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_SETTINGS_WINDOW]); + settingsWindow.DrawSettings(&running); + ImGui::PopFont(); + + if (!running) { + Finish(); + } +} + +void OpenInGameSettingsDialog() { + if (settingsLayer.has_value()) { + return; + } + + settingsLayer.emplace(); + running = true; +} + +} // namespace ImGuiEmuSettings diff --git a/src/imgui/big_picture/settings_dialog_layer.h b/src/imgui/big_picture/settings_dialog_layer.h new file mode 100644 index 000000000..06a140a91 --- /dev/null +++ b/src/imgui/big_picture/settings_dialog_layer.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "imgui/imgui_layer.h" +#include "settings_dialog_imgui.h" + +namespace ImGuiEmuSettings { + +class SettingsLayer final : public ImGui::Layer { +public: + SettingsLayer(); + ~SettingsLayer() override; + + void Draw() override; + +private: + void Finish(); + SettingsWindow settingsWindow = SettingsWindow(true); +}; + +void OpenInGameSettingsDialog(); + +}; // namespace ImGuiEmuSettings diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 821ef1568..885d8f9a5 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -15,6 +15,7 @@ #define IMGUI_FONT_TEXT 0 #define IMGUI_FONT_MONO 1 #define IMGUI_FONT_TEXT_BIG 2 +#define IMGUI_FONT_SETTINGS_WINDOW 3 namespace ImGui { diff --git a/src/imgui/renderer/font_stack.cpp b/src/imgui/renderer/font_stack.cpp index a88109e3a..e5d88b7a9 100644 --- a/src/imgui/renderer/font_stack.cpp +++ b/src/imgui/renderer/font_stack.cpp @@ -25,6 +25,7 @@ constexpr int kNotoSansCjkFontIndexTc = 3; constexpr ImWchar kLatinExtendedRanges[] = { 0x0100, 0x024F, // Latin Extended-A + Latin Extended-B 0x1E00, 0x1EFF, // Latin Extended Additional + 0x2122, 0x2122, // TM symbol 0, }; diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 24537fd76..ba0118d33 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -75,6 +75,11 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w // Avoid exploding atlas size on Metal/MoltenVK when CJK fallback is enabled. FontStack::AddPrimaryUiFont(io.Fonts, 128.0f, console_language, font_cfg, false); + + // Big Picture + FontStack::AddPrimaryUiFont(ImGui::GetIO().Fonts, 64.0f, EmulatorSettings.GetConsoleLanguage(), + font_cfg, true); + io.Fonts->Build(); io.FontGlobalScale = 0.5f; diff --git a/src/imgui/settings_dialog_imgui.cpp b/src/imgui/settings_dialog_imgui.cpp deleted file mode 100644 index 7d637a6af..000000000 --- a/src/imgui/settings_dialog_imgui.cpp +++ /dev/null @@ -1,802 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include - -#include "core/devtools/layer.h" -#include "core/emulator_settings.h" -#include "imgui/imgui_std.h" -#include "settings_dialog_imgui.h" - -CMRC_DECLARE(res); -namespace BigPictureMode { - -//////////////////// options for comboboxes -const std::map languageMap = {{"Arabic", 21}, - {"Czech", 23}, - {"Danish", 14}, - {"Dutch", 6}, - {"English (United Kingdom)", 18}, - {"English (United States)", 1}, - {"Finnish", 12}, - {"French (Canada)", 22}, - {"French (France)", 2}, - {"German", 4}, - {"Greek", 25}, - {"Hungarian", 24}, - {"Indonesian", 29}, - {"Italian", 5}, - {"Japanese", 0}, - {"Korean", 9}, - {"Norwegian (Bokmaal)", 15}, - {"Polish", 16}, - {"Portuguese (Brazil)", 17}, - {"Portuguese (Portugal)", 7}, - {"Romanian", 26}, - {"Russian", 8}, - {"Simplified Chinese", 11}, - {"Spanish (Latin America)", 20}, - {"Spanish (Spain)", 3}, - {"Swedish", 13}, - {"Thai", 27}, - {"Traditional Chinese", 10}, - {"Turkish", 19}, - {"Ukrainian", 30}, - {"Vietnamese", 28}}; -std::vector languageOptions; // assigned from keys above -const std::vector fullscreenModeOptions = {"Windowed", "Fullscreen", - "Fullscreen (Borderless)"}; -const std::vector audioBackendOptions = {"SDL", "OpenAL"}; -const std::vector presentModeOptions = {"Mailbox", "Fifo", "Immediate"}; -const std::vector hideCursorOptions = {"Never", "Idle", "Always"}; -const std::vector trophySideOptions = {"left", "right", "top", "bottom"}; -const std::vector readbacksModeOptions = {"Disabled", "Relaxed", "Precise"}; - -//////////////// Setting Variables -//////////////// Note:: Use int for all comboboxes as needed by ImGui - -// General tab -int consoleLanguageSetting; -int volumeSetting; -bool showSplashSetting; -int audioBackendSetting; - -// Graphics tab -int fullscreenModeSetting; -int presentModeSetting; -int windowWidthSetting; -int windowHeightSetting; -bool hdrAllowedSetting; -bool fsrEnabledSetting; -bool rcasEnabledSetting; -float rcasAttenuationSetting; - -// Input tab -bool motionControlsSetting; -bool backgroundControllerSetting; -int cursorStateSetting; -int cursorTimeoutSetting; - -// Trophy tab -bool trophyPopupDisabledSetting; -int trophySideSetting; -float trophyDurationSetting; - -// Log tab -bool logEnableSetting; -bool logSeparateSetting; -bool logSyncSetting; - -// Experimental tab -int readbacksModeSetting; -bool readbackLinearImagesSetting; -bool directMemoryAccessSetting; -bool devkitConsoleSetting; -bool neoModeSetting; -bool shadnetEnabledSetting; -bool connectedNetworkSetting; -bool pipelineCacheEnabledSetting; -bool pipelineCacheArchiveSetting; -int extraDmemSetting; -int vblankFrequencySetting; - -//////////////// Texture data -SDL_Texture* profilesTexture; -SDL_Texture* generalTexture; -SDL_Texture* globalSettingsTexture; -SDL_Texture* experimentalTexture; -SDL_Texture* graphicsTexture; -SDL_Texture* inputTexture; -SDL_Texture* trophyTexture; -SDL_Texture* logTexture; -SDL_Texture* foldersTexture; - -//////////////// Gui variable -const float gameImageSize = 200.f; -const float settingsIconSize = 125.f; -std::vector settingsProfileVec = {}; -std::vector m_GameInstallDirs = {}; - -float uiScale = 1.0f; -SDL_Renderer* renderer; - -SettingsCategory currentCategory = SettingsCategory::Profiles; -std::string currentProfile = "Global"; -bool closeOnSave = false; -bool showFileDialog = false; - -void Init() { - auto languageKeys = std::views::keys(languageMap); - languageOptions.assign(languageKeys.begin(), languageKeys.end()); - - currentProfile = "Global"; - currentCategory = SettingsCategory::Profiles; - LoadSettings("Global"); - m_GameInstallDirs = EmulatorSettings.GetAllGameInstallDirs(); - - SDL_Window* window = SDL_GetKeyboardFocus(); - renderer = SDL_GetRenderer(window); - - LoadEmbeddedTexture("src/images/big_picture/settings.png", generalTexture); - LoadEmbeddedTexture("src/images/big_picture/profiles.png", profilesTexture); - LoadEmbeddedTexture("src/images/big_picture/global-settings.png", globalSettingsTexture); - LoadEmbeddedTexture("src/images/big_picture/experimental.png", experimentalTexture); - LoadEmbeddedTexture("src/images/big_picture/graphics.png", graphicsTexture); - LoadEmbeddedTexture("src/images/big_picture/controller.png", inputTexture); - LoadEmbeddedTexture("src/images/big_picture/trophy.png", trophyTexture); - LoadEmbeddedTexture("src/images/big_picture/log.png", logTexture); - LoadEmbeddedTexture("src/images/big_picture/folder.png", foldersTexture); - - GetGameInfo(settingsProfileVec, true, globalSettingsTexture); - uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); -} - -void DeInit() { - EmulatorSettings.Load(); - EmulatorSettings.SetBigPictureScale(static_cast(uiScale * 1000)); - EmulatorSettings.Save(); -} - -void DrawSettings(bool* open) { - if (!*open) - return; - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->WorkPos); - ImGui::SetNextWindowSize(viewport->WorkSize); - - if (ImGui::Begin("Settings", nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::IsWindowAppearing()) { - Init(); - closeOnSave = false; - } - - ImGui::DrawPrettyBackground(); - ImGui::SetWindowFontScale(uiScale); - ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; - - ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray - ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor); - float vertSize = (settingsIconSize * uiScale + ImGui::CalcTextSize("Profiles").y) + - ImGui::GetStyle().FramePadding.x * 2.f * uiScale + 20.0 * uiScale; - - ImGui::BeginChild("Categories", ImVec2(0, vertSize), true, - child_flags | ImGuiWindowFlags_HorizontalScrollbar); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30.0f * uiScale, 0.0f)); - - // Must add categories in enum order for L1/R1 to work correctly, with experimental last - AddCategory("Profiles", profilesTexture, SettingsCategory::Profiles); - AddCategory("General", generalTexture, SettingsCategory::General); - AddCategory("Graphics", graphicsTexture, SettingsCategory::Graphics); - AddCategory("Input", inputTexture, SettingsCategory::Input); - AddCategory("Trophy", trophyTexture, SettingsCategory::Trophy); - AddCategory("Game Folders", foldersTexture, SettingsCategory::Folders); - AddCategory("Log", logTexture, SettingsCategory::Log); - - if (currentProfile != "Global") - AddCategory("Experimental", experimentalTexture, SettingsCategory::Experimental); - - ImGui::PopStyleVar(); - ImGui::EndChild(); // Categories - - if (ImGui::IsWindowAppearing()) { - ImGui::SetKeyboardFocusHere(); - } - - ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true, - child_flags); - ImGui::PopStyleColor(); - - LoadCategory(currentCategory); - - ImGui::EndChild(); - ImGui::Separator(); - - ImGui::SetNextItemWidth(300.0f * uiScale); - static float sliderScale2 = 1.0f; - if (ImGui::IsWindowAppearing()) { - sliderScale2 = uiScale; - } - - ImGui::SliderFloat("UI Scale", &sliderScale2, 0.25f, 3.0f); - // Only update when user is not interacting with slider - if (ImGui::IsItemDeactivatedAfterEdit()) { - uiScale = sliderScale2; - } - ImGui::SameLine(); - - // Align buttons right - float buttonsWidth = ImGui::CalcTextSize("Save").x + ImGui::CalcTextSize("Cancel").x + - ImGui::CalcTextSize("Apply").x + - ImGui::GetStyle().FramePadding.x * 6.0f + - ImGui::GetStyle().ItemSpacing.x * 2; - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - buttonsWidth); - - if (ImGui::Button("Save")) { - closeOnSave = true; - ImGui::OpenPopup("Save Confirmation"); - } - - ImGui::SameLine(); - if (ImGui::Button("Apply")) { - ImGui::OpenPopup("Save Confirmation"); - } - - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - if (ImGui::BeginPopupModal("Save Confirmation", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("%s", ("Profile Saved:\n" + currentProfile).c_str()); - ImGui::Separator(); - - if (ImGui::Button("OK", ImVec2(250 * uiScale, 0))) { - std::string profile = currentProfile; - if (currentProfile != "Global") { - profile = currentProfile.substr(0, 9); - } - - SaveSettings(profile); - if (closeOnSave) { - DeInit(); - *open = false; - ImGui::CloseCurrentPopup(); - } else { - ImGui::CloseCurrentPopup(); - } - } - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - DeInit(); - *open = false; - } - - SettingsCategory lastCategory = - currentProfile != "Global" ? SettingsCategory::Experimental : SettingsCategory::Log; - // Navigate categories with Tab / R1 / L1 - if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1) || ImGui::IsKeyPressed(ImGuiKey_Tab)) { - int currentIndex = static_cast(currentCategory); - currentCategory == lastCategory - ? currentCategory = static_cast(0) - : currentCategory = static_cast(currentIndex + 1); - } - - if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) { - int currentIndex = static_cast(currentCategory); - currentIndex == 0 ? currentCategory = lastCategory - : currentCategory = static_cast(currentIndex - 1); - } - } - - ImGui::End(); -} - -void LoadCategory(SettingsCategory category) { - if (category != SettingsCategory::Folders) { - ImGui::TextColored(ImVec4(0.00f, 1.00f, 1.00f, 1.00f), "%s", - ("Selected Profile: " + currentProfile).c_str()); // Dark Blue - } - ImGui::Dummy(ImVec2(0, 20.f * uiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f * uiScale, 10.0f * uiScale)); - - ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; - - if (category == SettingsCategory::General) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingCombo("Console Language", consoleLanguageSetting, languageOptions); - AddSettingSliderInt("Volume", volumeSetting, 0, 500); - AddSettingBool("Show Splash Screen When Launching Game", showSplashSetting); - AddSettingCombo("Audio Backend", audioBackendSetting, audioBackendOptions); - - ImGui::EndTable(); - } - } else if (category == SettingsCategory::Graphics) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingCombo("Display Mode", fullscreenModeSetting, fullscreenModeOptions); - AddSettingCombo("Present Mode", presentModeSetting, presentModeOptions); - AddSettingSliderInt("Window Width", windowWidthSetting, 0, 8000); - AddSettingSliderInt("Window Height", windowHeightSetting, 0, 7000); - AddSettingBool("Enable HDR", hdrAllowedSetting); - AddSettingBool("Enable FSR", fsrEnabledSetting); - - if (fsrEnabledSetting) { - AddSettingBool("Enable RCAS", rcasEnabledSetting); - } - - if (rcasEnabledSetting && fsrEnabledSetting) { - AddSettingSliderFloat("RCAS Attenuation", rcasAttenuationSetting, 0.0f, 3.0f, 3); - } - - ImGui::EndTable(); - } - } else if (category == SettingsCategory::Input) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingBool("Enable Motion Controls", motionControlsSetting); - AddSettingBool("Enable Background Controller Input", backgroundControllerSetting); - AddSettingCombo("Hide Cursor", cursorStateSetting, hideCursorOptions); - - if (cursorStateSetting == 1) { - AddSettingSliderInt("Hide Cursor Idle Timeout", cursorTimeoutSetting, 1, 10); - } - - ImGui::EndTable(); - } - } else if (category == SettingsCategory::Trophy) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingBool("Disable Trophy Notification", trophyPopupDisabledSetting); - if (!trophyPopupDisabledSetting) { - AddSettingCombo("Trophy Notification Position", trophySideSetting, - trophySideOptions); - AddSettingSliderFloat("Trophy Notification Duration", trophyDurationSetting, 0.f, - 10.f, 1); - } - - ImGui::EndTable(); - } - } else if (category == SettingsCategory::Folders) { - if (ImGui::Button("Add Folder", ImVec2(400.f * uiScale, 0))) { - ImGuiFileDialog::Instance()->OpenDialog( - "OpenFolder", "Add shadPS4 game folder", nullptr, ".", 1, nullptr, - ImGuiFileDialogFlags_DisableCreateDirectoryButton | - ImGuiFileDialogFlags_DontShowHiddenFiles); - - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - } - - if (ImGuiFileDialog::Instance()->Display("OpenFolder", - child_flags | ImGuiWindowFlags_NoMove)) { - if (ImGuiFileDialog::Instance()->IsOk()) { - GameInstallDir dir; - dir.path = ImGuiFileDialog::Instance()->GetCurrentPath(); - dir.enabled = true; - -#ifdef WIN32 - // replace \ with / on windows - std::string pathString = dir.path.string(); - size_t pos = 0; - while ((pos = pathString.find("\\", pos)) != std::string::npos) { - pathString.replace(pos, 1, "/"); - pos += 2; - } - - dir.path = pathString; -#endif - - m_GameInstallDirs.push_back(dir); - SaveInstallDirs(); - } - - ImGuiFileDialog::Instance()->Close(); - } - - } else if (category == SettingsCategory::Log) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingBool("Enable Logging", logEnableSetting); - if (logEnableSetting) { - AddSettingBool("Separate Log Files", logSeparateSetting); - AddSettingBool("Log Sync", logSyncSetting); - } - - ImGui::EndTable(); - } - } else if (category == SettingsCategory::Experimental) { - if (ImGui::BeginTable("SettingsTable", 2)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); - ImGui::TableSetupColumn("Value"); - - AddSettingSliderInt("Additional DMem Allocation", extraDmemSetting, 0, 20000); - AddSettingSliderInt("Vblank Frequency", vblankFrequencySetting, 30, 360); - AddSettingCombo("Readbacks Mode", readbacksModeSetting, readbacksModeOptions); - AddSettingBool("Enable Readback Linear Images", readbackLinearImagesSetting); - AddSettingBool("Enable Direct Memory Access", directMemoryAccessSetting); - AddSettingBool("Enable Devkit Console Mode", devkitConsoleSetting); - AddSettingBool("Enable PS4 Neo Mode", neoModeSetting); - AddSettingBool("Enable ShadNet", shadnetEnabledSetting); - AddSettingBool("Set Network Connected to True", connectedNetworkSetting); - AddSettingBool("Enable Shader Cache", pipelineCacheEnabledSetting); - - if (pipelineCacheEnabledSetting) { - AddSettingBool("Compress Shader Cache to Zip File", pipelineCacheArchiveSetting); - } - - ImGui::EndTable(); - } - } - - ImGui::PopStyleVar(); - - // Child Window if Needed - if (category == SettingsCategory::Profiles) { - - ImGui::BeginChild("ProfileSelect", ImVec2(0, 0), true, child_flags); - Overlay::TextCentered("Select Global or Game-Specific Settings Profile"); - SetProfileIcons(settingsProfileVec); - ImGui::EndChild(); - } else if (category == SettingsCategory::Folders) { - ImGui::BeginChild("Game Folder List", ImVec2(0, 0), true, child_flags); - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 5.0f * uiScale)); - if (ImGui::BeginTable("FoldersTable", 3)) { - ImGui::TableSetupColumn("FolderButton", ImGuiTableColumnFlags_WidthFixed, - 300.0f * uiScale); - ImGui::TableSetupColumn("FolderEnabled", ImGuiTableColumnFlags_WidthFixed, 0.0f); - ImGui::TableSetupColumn("FolderPath"); - - for (int i = 0; i < m_GameInstallDirs.size(); i++) { - std::string buttonLabel = "Remove Folder##" + std::to_string(i); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Button(buttonLabel.c_str(), ImVec2(280 * uiScale, 0))) { - m_GameInstallDirs.erase(m_GameInstallDirs.begin() + i); - SaveInstallDirs(); - } - - ImGui::TableNextColumn(); - std::string checkboxLabel = "##EnableFolder" + std::to_string(i); - bool previousState = m_GameInstallDirs[i].enabled; - ImGui::Checkbox(checkboxLabel.c_str(), &m_GameInstallDirs[i].enabled); - ImGui::SameLine(); - ImGui::Dummy(ImVec2(5.0 * uiScale, 0)); - - if (m_GameInstallDirs[i].enabled != previousState) { - SaveInstallDirs(); - } - - ImGui::TableNextColumn(); - ImGui::TextWrapped("%s", m_GameInstallDirs[i].path.string().c_str()); - } - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - - ImGui::EndChild(); - } -} - -void SaveSettings(std::string profile) { - const bool isSpecific = currentProfile != "Global"; - - /////////// General Tab - EmulatorSettings.SetConsoleLanguage(languageMap.at(languageOptions.at(consoleLanguageSetting)), - isSpecific); - EmulatorSettings.SetVolumeSlider(volumeSetting, isSpecific); - EmulatorSettings.SetShowSplash(showSplashSetting, isSpecific); - EmulatorSettings.SetAudioBackend(audioBackendSetting, isSpecific); - - /////////// Graphics Tab - bool isFullscreen = fullscreenModeSetting != 0; - EmulatorSettings.SetFullScreen(isFullscreen); - EmulatorSettings.SetFullScreenMode(fullscreenModeOptions.at(fullscreenModeSetting), isSpecific); - EmulatorSettings.SetPresentMode(presentModeOptions.at(presentModeSetting), isSpecific); - EmulatorSettings.SetWindowHeight(windowHeightSetting, isSpecific); - EmulatorSettings.SetWindowWidth(windowWidthSetting, isSpecific); - EmulatorSettings.SetHdrAllowed(hdrAllowedSetting, isSpecific); - EmulatorSettings.SetFsrEnabled(fsrEnabledSetting, isSpecific); - EmulatorSettings.SetRcasEnabled(rcasEnabledSetting, isSpecific); - EmulatorSettings.SetRcasAttenuation(static_cast(rcasAttenuationSetting * 1000), - isSpecific); - - /////////// Input Tab - EmulatorSettings.SetMotionControlsEnabled(motionControlsSetting, isSpecific); - EmulatorSettings.SetBackgroundControllerInput(backgroundControllerSetting, isSpecific); - EmulatorSettings.SetCursorState(cursorStateSetting, isSpecific); - EmulatorSettings.SetCursorHideTimeout(cursorTimeoutSetting, isSpecific); - - /////////// Trophy Tab - EmulatorSettings.SetTrophyPopupDisabled(trophyPopupDisabledSetting, isSpecific); - EmulatorSettings.SetTrophyNotificationSide(trophySideOptions.at(trophySideSetting), isSpecific); - EmulatorSettings.SetTrophyNotificationDuration(static_cast(trophyDurationSetting)); - - /////////// Log Tab - EmulatorSettings.SetLogEnable(logEnableSetting, isSpecific); - EmulatorSettings.SetLogSeparate(logSeparateSetting, isSpecific); - EmulatorSettings.SetLogSync(logSyncSetting, isSpecific); - - /////////// Experimental Tab - if (isSpecific) { - EmulatorSettings.SetReadbacksMode(readbacksModeSetting, true); - EmulatorSettings.SetReadbackLinearImagesEnabled(readbackLinearImagesSetting, true); - EmulatorSettings.SetDirectMemoryAccessEnabled(directMemoryAccessSetting, true); - EmulatorSettings.SetDevKit(devkitConsoleSetting, true); - EmulatorSettings.SetNeo(neoModeSetting, true); - EmulatorSettings.SetShadNetEnabled(shadnetEnabledSetting, true); - EmulatorSettings.SetConnectedToNetwork(connectedNetworkSetting, true); - EmulatorSettings.SetPipelineCacheEnabled(pipelineCacheEnabledSetting, true); - EmulatorSettings.SetPipelineCacheArchived(pipelineCacheArchiveSetting, true); - EmulatorSettings.SetExtraDmemInMBytes(extraDmemSetting, true); - EmulatorSettings.SetVblankFrequency(vblankFrequencySetting, true); - } - - if (!isSpecific) { - EmulatorSettings.Save(); - } else { - EmulatorSettings.Save(profile); - } -} - -void LoadSettings(std::string profile) { - const bool isSpecific = currentProfile != "Global"; - if (!isSpecific) { - EmulatorSettings.Load(); - } else { - EmulatorSettings.Load(profile); - } - - /////////// General Tab - int languageIndex = EmulatorSettings.GetConsoleLanguage(); - std::string language; - for (const auto& [key, value] : languageMap) { - if (value == languageIndex) { - language = key; - } - } - consoleLanguageSetting = GetComboIndex(language, languageOptions); - volumeSetting = EmulatorSettings.GetVolumeSlider(); - showSplashSetting = EmulatorSettings.IsShowSplash(); - audioBackendSetting = EmulatorSettings.GetAudioBackend(); - - /////////// Graphics Tab - fullscreenModeSetting = - GetComboIndex(EmulatorSettings.GetFullScreenMode(), fullscreenModeOptions); - presentModeSetting = GetComboIndex(EmulatorSettings.GetPresentMode(), presentModeOptions); - windowHeightSetting = EmulatorSettings.GetWindowHeight(); - windowWidthSetting = EmulatorSettings.GetWindowWidth(); - hdrAllowedSetting = EmulatorSettings.IsHdrAllowed(); - fsrEnabledSetting = EmulatorSettings.IsFsrEnabled(); - rcasEnabledSetting = EmulatorSettings.IsRcasEnabled(); - rcasAttenuationSetting = static_cast(EmulatorSettings.GetRcasAttenuation() * 0.001f); - - /////////// Input Tab - motionControlsSetting = EmulatorSettings.IsMotionControlsEnabled(); - backgroundControllerSetting = EmulatorSettings.IsBackgroundControllerInput(); - cursorStateSetting = EmulatorSettings.GetCursorState(); - cursorTimeoutSetting = EmulatorSettings.GetCursorHideTimeout(); - - /////////// Trophy Tab - trophyPopupDisabledSetting = EmulatorSettings.IsTrophyPopupDisabled(); - trophySideSetting = - GetComboIndex(EmulatorSettings.GetTrophyNotificationSide(), trophySideOptions); - trophyDurationSetting = static_cast(EmulatorSettings.GetTrophyNotificationDuration()); - - /////////// Log Tab - logEnableSetting = EmulatorSettings.IsLogEnable(); - logSeparateSetting = EmulatorSettings.IsLogSeparate(); - logSyncSetting = EmulatorSettings.IsLogSync(); - - /////////// Experimental Tab - if (isSpecific) { - readbacksModeSetting = EmulatorSettings.GetReadbacksMode(); - readbackLinearImagesSetting = EmulatorSettings.IsReadbackLinearImagesEnabled(); - directMemoryAccessSetting = EmulatorSettings.IsDirectMemoryAccessEnabled(); - devkitConsoleSetting = EmulatorSettings.IsDevKit(); - neoModeSetting = EmulatorSettings.IsNeo(); - shadnetEnabledSetting = EmulatorSettings.IsShadNetEnabled(); - connectedNetworkSetting = EmulatorSettings.IsConnectedToNetwork(); - pipelineCacheEnabledSetting = EmulatorSettings.IsPipelineCacheEnabled(); - pipelineCacheArchiveSetting = EmulatorSettings.IsPipelineCacheArchived(); - extraDmemSetting = EmulatorSettings.GetExtraDmemInMBytes(); - vblankFrequencySetting = EmulatorSettings.GetVblankFrequency(); - } -} - -void AddCategory(std::string name, SDL_Texture* texture, SettingsCategory category) { - ImGui::SameLine(); - ImGui::BeginGroup(); - - // make button appear hovered as long as category is selected, otherwise dull it's hovered color - currentCategory == category - ? ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]) - : ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.235f, 0.392f, 0.624f, 1.00f)); - - if (ImGui::ImageButton(name.c_str(), ImTextureID(texture), - ImVec2(settingsIconSize * uiScale, settingsIconSize * uiScale))) { - currentCategory = category; - } - - ImGui::PopStyleColor(); - - ImGui::SetCursorPosX( - (ImGui::GetCursorPosX() + - (settingsIconSize * uiScale - ImGui::CalcTextSize(name.c_str()).x) * 0.5f) + - ImGui::GetStyle().FramePadding.x); - ImGui::Text("%s", name.c_str()); - ImGui::EndGroup(); -} - -void AddSettingBool(std::string name, bool& value) { - std::string label = "##" + name; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGui::TextWrapped("%s", name.c_str()); - ImGui::TableNextColumn(); - ImGui::Checkbox(label.c_str(), &value); -} - -void AddSettingSliderInt(std::string name, int& value, int min, int max) { - std::string label = "##" + name; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextWrapped("%s", name.c_str()); - - ImGui::TableNextColumn(); - ImGui::SliderInt(label.c_str(), &value, min, max); -} - -void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision) { - std::string label = "##" + name; - std::string precisionString = "%." + std::to_string(precision) + "f"; - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextWrapped("%s", name.c_str()); - - ImGui::TableNextColumn(); - ImGui::SliderFloat(label.c_str(), &value, min, max, precisionString.c_str()); -} - -void AddSettingCombo(std::string name, int& value, std::vector options) { - std::string label = "##" + name; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextWrapped("%s", name.c_str()); - - ImGui::TableNextColumn(); - const char* combo_value = options[value].c_str(); - if (ImGui::BeginCombo(label.c_str(), combo_value)) { - for (int i = 0; i < options.size(); i++) { - const bool selected = (i == value); - if (ImGui::Selectable(options[i].c_str(), selected)) - value = i; - - // Set the initial focus when opening the combo - if (selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } -} - -int GetComboIndex(std::string selection, std::vector options) { - for (int i = 0; i < options.size(); i++) { - if (selection == options[i]) - return i; - } - - return 0; -} - -void LoadEmbeddedTexture(std::string resourcePath, SDL_Texture*& texture) { - auto resource = cmrc::res::get_filesystem(); - auto file = resource.open(resourcePath); - std::vector texData = std::vector(file.begin(), file.end()); - - BigPictureMode::LoadTextureData(texData, texture, renderer); -} - -void SetProfileIcons(std::vector& games) { - ImGuiStyle& style = ImGui::GetStyle(); - const float maxAvailableWidth = ImGui::GetContentRegionAvail().x; - const float itemSpacing = style.ItemSpacing.x; // already scaled - const float padding = 10.0f * uiScale; - float rowContentWidth = gameImageSize * uiScale + itemSpacing; - - for (int i = 0; i < games.size(); i++) { - ImGui::BeginGroup(); - std::string ButtonName = "Button" + std::to_string(i); - const char* ButtonNameChar = ButtonName.c_str(); - - bool isNextItemFocused = (ImGui::GetID(ButtonNameChar) == ImGui::GetFocusID()); - bool popColor = false; - if (isNextItemFocused) { - ImGui::PushStyleColor(ImGuiCol_Button, - ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); - popColor = true; - } - - if (ImGui::ImageButton(ButtonNameChar, (ImTextureID)games[i].iconTexture, - ImVec2(gameImageSize * uiScale, gameImageSize * uiScale))) { - currentProfile = i == 0 ? "Global" : games[i].serial + " - " + games[i].title; - LoadSettings(games[i].serial); - } - - if (popColor) { - ImGui::PopStyleColor(); - } - - // Scroll to item only when newly-focused - if (ImGui::IsItemFocused() && !games[i].focusState) { - ImGui::SetScrollHereY(0.5f); - } - - if (ImGui::IsWindowFocused()) { - games[i].focusState = ImGui::IsItemFocused(); - } - - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale); - ImGui::TextWrapped("%s", games[i].title.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndGroup(); - - // Use same line if content fits horizontally, move to next line if not - rowContentWidth += (gameImageSize * uiScale + itemSpacing * 2 + padding); - if (rowContentWidth < maxAvailableWidth) { - ImGui::SameLine(0.0f, padding); - } else { - ImGui::Dummy(ImVec2(0.0f, padding)); - rowContentWidth = gameImageSize * uiScale + itemSpacing; - } - } -} - -void SaveInstallDirs() { - std::string profile; - const bool isGlobal = currentProfile == "Global"; - if (!isGlobal) { - profile = currentProfile.substr(0, 9); - } - - if (!isGlobal) { - EmulatorSettings.Load(); - } - - EmulatorSettings.SetAllGameInstallDirs(m_GameInstallDirs); - EmulatorSettings.Save(); - - if (!isGlobal) { - EmulatorSettings.Load(profile); - } - - GetGameInfo(settingsProfileVec, true, globalSettingsTexture); -} - -} // namespace BigPictureMode diff --git a/src/imgui/settings_dialog_imgui.h b/src/imgui/settings_dialog_imgui.h deleted file mode 100644 index 4db517b0a..000000000 --- a/src/imgui/settings_dialog_imgui.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "big_picture.h" - -namespace BigPictureMode { - -enum class SettingsCategory { - Profiles, - General, - Graphics, - Input, - Trophy, - Folders, - Log, - Experimental, -}; - -void Init(); -void DeInit(); - -void SetProfileIcons(std::vector& games); -void LoadEmbeddedTexture(std::string resourcePath, SDL_Texture*& texture); -void AddCategory(std::string name, SDL_Texture* texture, SettingsCategory category); - -void DrawSettings(bool* open); -void SaveSettings(std::string profile); -void LoadSettings(std::string profile); -void LoadCategory(SettingsCategory); -void SaveInstallDirs(); - -void AddSettingBool(std::string name, bool& value); -void AddSettingSliderInt(std::string name, int& value, int min, int max); -void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision); -void AddSettingCombo(std::string name, int& value, std::vector options); -int GetComboIndex(std::string selection, std::vector options); - -} // namespace BigPictureMode diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 35433393e..bdd7d008a 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -25,6 +25,7 @@ #include "core/devtools/layer.h" #include "core/emulator_settings.h" #include "core/emulator_state.h" +#include "imgui/big_picture/settings_dialog_layer.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -180,6 +181,7 @@ std::filesystem::path GetInputConfigFile(const std::string& game_id) { {"hotkey_quit", "lctrl, lshift, end"}, {"hotkey_volume_up", "kpplus"}, {"hotkey_volume_down", "kpminus"}, + {"hotkey_emulator_settings", "f3"}, }; std::string legacy_capture_binding; bool legacy_capture_binding_found = false; @@ -799,6 +801,9 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) { case HOTKEY_QUIT: PushSDLEvent(SDL_EVENT_QUIT_DIALOG); break; + case HOTKEY_OPEN_EMULATOR_SETTINGS: + ImGuiEmuSettings::OpenInGameSettingsDialog(); + break; case KEY_TOGGLE: // noop break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 5e7d200d2..2b9529fdc 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -64,6 +64,7 @@ #define HOTKEY_ADD_VIRTUAL_USER 0xf000000c #define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d #define HOTKEY_SCREENSHOT_WITH_OVERLAYS 0xf000000e +#define HOTKEY_OPEN_EMULATOR_SETTINGS 0xf000000f #define SDL_UNMAPPED UINT32_MAX - 1 @@ -165,6 +166,7 @@ const std::map string_to_hotkey_map = { {"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER}, {"hotkey_volume_up", HOTKEY_VOLUME_UP}, {"hotkey_volume_down", HOTKEY_VOLUME_DOWN}, + {"hotkey_emulator_settings", HOTKEY_OPEN_EMULATOR_SETTINGS}, }; const std::map string_to_axis_map = { @@ -530,7 +532,7 @@ public: class ControllerAllOutputs { public: - static constexpr u64 output_count = 41; + static constexpr u64 output_count = 42; std::array data = { // Important: these have to be the first, or else they will update in the wrong order ControllerOutput(LEFTJOYSTICK_HALFMODE), @@ -583,6 +585,7 @@ public: ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER), ControllerOutput(HOTKEY_VOLUME_UP), ControllerOutput(HOTKEY_VOLUME_DOWN), + ControllerOutput(HOTKEY_OPEN_EMULATOR_SETTINGS), ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), }; diff --git a/src/main.cpp b/src/main.cpp index 3baf3cf76..8576add0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ #include "core/file_sys/fs.h" #include "core/ipc/ipc.h" #include "emulator.h" -#include "imgui/big_picture.h" +#include "imgui/big_picture/big_picture.h" #ifdef _WIN32 #include