diff --git a/.gitmodules b/.gitmodules index 660bb3046..042e83586 100644 --- a/.gitmodules +++ b/.gitmodules @@ -125,9 +125,6 @@ [submodule "externals/openal-soft"] path = externals/openal-soft url = https://github.com/shadexternals/openal-soft.git -[submodule "externals/sdl3_image"] - path = externals/sdl3_image - url = https://github.com/libsdl-org/SDL_image [submodule "externals/libusb"] path = externals/libusb url = https://github.com/shadexternals/libusb.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f29e03a7..cd13a96c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,6 @@ find_package(nlohmann_json 3.12 CONFIG) find_package(PNG 1.6 MODULE) find_package(OpenAL CONFIG) find_package(RenderDoc 1.6.0 MODULE) -find_package(SDL3_image CONFIG) find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) find_package(toml11 4.2.0 CONFIG) @@ -1107,6 +1106,8 @@ set(IMGUI src/imgui/imgui_config.h 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 ) set(INPUT src/input/controller.cpp @@ -1144,7 +1145,7 @@ add_executable(shadps4 create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG minimp3) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_image::SDL3_image pugixml::pugixml) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml) target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") @@ -1267,6 +1268,14 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeRC.cmake") cmrc_add_resource_library(embedded-resources ALIAS res::embedded 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 + src/images/big_picture/trophy.png + src/images/big_picture/log.png src/images/trophy.wav src/images/bronze.png src/images/gold.png diff --git a/REUSE.toml b/REUSE.toml index e8997f007..0d1505bab 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -76,6 +76,14 @@ path = [ "src/images/trophy.wav", "src/images/hotkey.png", "src/images/game_settings.png", + "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", + "src/images/big_picture/trophy.png", "src/shadps4.rc", ] precedence = "aggregate" diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 735552485..187c2f435 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -63,29 +63,6 @@ if (NOT TARGET SDL3::SDL3) add_subdirectory(sdl3) endif() -# SDL3_image -if (NOT TARGET SDL3_image::SDL3_image) - set(SDLIMAGE_VENDORED OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_ANI OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_AVIF OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_BMP OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_GIF OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_JPG OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_JXL OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_LBM OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_PCX OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_PNM OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_QOI OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_SVG OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_TGA OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_TIF OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_WEBP OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_XCF OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_XPM OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_XV OFF CACHE BOOL "" FORCE) - add_subdirectory(sdl3_image) -endif() - # vulkan-headers if (NOT TARGET Vulkan::Headers) set(VULKAN_HEADERS_ENABLE_MODULE OFF) diff --git a/externals/sdl3_image b/externals/sdl3_image deleted file mode 160000 index 1aedddcbd..000000000 --- a/externals/sdl3_image +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1aedddcbd205c4e1ea0f99fdb2c785acc8e2489b diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index db28e2ec1..30fc5d6c1 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -484,11 +484,11 @@ void L::Draw() { namespace Overlay { void TextCentered(const std::string& text) { - float window_width = GetWindowSize().x; + float window_width = GetContentRegionAvail().x; float text_width = CalcTextSize(text.c_str()).x; float text_indentation = (window_width - text_width) * 0.5f; - SameLine(text_indentation); + SetCursorPosX(text_indentation); Text("%s", text.c_str()); } diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 660a1a1a4..211f2e478 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -72,7 +72,6 @@ struct Setting { } /// Write v to the base layer. - /// Game-specific overrides are applied exclusively via Load(serial) /// Set proper value as base or game_specific void set(const T& v, bool game_specific = false) { if (game_specific) { diff --git a/src/images/big_picture/controller.png b/src/images/big_picture/controller.png new file mode 100644 index 000000000..398aa176a Binary files /dev/null and b/src/images/big_picture/controller.png differ diff --git a/src/images/big_picture/experimental.png b/src/images/big_picture/experimental.png new file mode 100644 index 000000000..8f789dd60 Binary files /dev/null and b/src/images/big_picture/experimental.png differ diff --git a/src/images/big_picture/folder.png b/src/images/big_picture/folder.png new file mode 100644 index 000000000..d36d2440d Binary files /dev/null and b/src/images/big_picture/folder.png differ diff --git a/src/images/big_picture/global-settings.png b/src/images/big_picture/global-settings.png new file mode 100644 index 000000000..47d384f7d Binary files /dev/null and b/src/images/big_picture/global-settings.png differ diff --git a/src/images/big_picture/graphics.png b/src/images/big_picture/graphics.png new file mode 100644 index 000000000..66fce3d4f Binary files /dev/null and b/src/images/big_picture/graphics.png differ diff --git a/src/images/big_picture/log.png b/src/images/big_picture/log.png new file mode 100644 index 000000000..dc215c328 Binary files /dev/null and b/src/images/big_picture/log.png differ diff --git a/src/images/big_picture/settings.png b/src/images/big_picture/settings.png new file mode 100644 index 000000000..b2dbed3ee Binary files /dev/null and b/src/images/big_picture/settings.png differ diff --git a/src/images/big_picture/trophy.png b/src/images/big_picture/trophy.png new file mode 100644 index 000000000..2fbce8901 Binary files /dev/null and b/src/images/big_picture/trophy.png differ diff --git a/src/imgui/big_picture.cpp b/src/imgui/big_picture.cpp index 8cf83db02..eb7ff0b5d 100644 --- a/src/imgui/big_picture.cpp +++ b/src/imgui/big_picture.cpp @@ -1,35 +1,38 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include #include +#include #include "big_picture.h" #include "common/logging/log.h" #include "core/devtools/layer.h" #include "core/file_format/psf.h" #include "emulator.h" +#include "imgui/imgui_std.h" #include "imgui/renderer/imgui_impl_sdl3_bpm.h" #include "imgui/renderer/imgui_impl_sdlrenderer3.h" +#include "settings_dialog_imgui.h" #include "imgui_fonts/notosansjp_regular.ttf.g.cpp" #include "imgui_fonts/proggyvector_regular.ttf.g.cpp" +CMRC_DECLARE(res); + namespace BigPictureMode { const float gameImageSize = 200.f; static bool done = false; -static bool runGame = false; +static bool showSettings = false; + static std::filesystem::path runEbootPath = ""; static std::vector gameVec = {}; -static std::vector focusState = {}; static float uiScale = 1.0f; -static int scaleSelected = 1; - -static SDL_Window* window = nullptr; -static SDL_Renderer* renderer = nullptr; +static SDL_Renderer* renderer; void Launch() { if (!SDL_Init(SDL_INIT_VIDEO)) { @@ -44,8 +47,9 @@ void Launch() { return; } - window = SDL_CreateWindow("shadPS4 Big Picture Mode", EmulatorSettings.GetWindowWidth(), - EmulatorSettings.GetWindowHeight(), SDL_WINDOW_RESIZABLE); + SDL_Window* window = + SDL_CreateWindow("shadPS4 Big Picture Mode", EmulatorSettings.GetWindowWidth(), + EmulatorSettings.GetWindowHeight(), SDL_WINDOW_RESIZABLE); renderer = SDL_CreateRenderer(window, nullptr); if (EmulatorSettings.IsFullScreen()) { @@ -109,18 +113,21 @@ void Launch() { colors[ImGuiCol_HeaderHovered] = ImVec4(0.25f, 0.50f, 0.85f, 1.00f); // lighter blue style.WindowRounding = 0.0f; - style.FrameRounding = 5.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; + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); ImGui_ImplSDL3_InitForSDLRenderer(window, renderer); ImGui_ImplSDLRenderer3_Init(renderer); - GetGameInfo(); + GetGameInfo(gameVec, false); uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); - float tempScale = uiScale; while (!done) { SDL_Event event; @@ -134,14 +141,15 @@ void Launch() { ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); + ImGui::PushFont(myFont); ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); ImGui::SetNextWindowSize(viewport->WorkSize); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration; - ImGui::PushFont(myFont); ImGui::Begin("Game Window", &done, window_flags); + ImGui::DrawPrettyBackground(); ImGui::SetWindowFontScale(uiScale); ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | @@ -160,31 +168,35 @@ void Launch() { ImGui::SetKeyboardFocusHere(); } - SetGameIcons(); + SetGameIcons(gameVec); ImGui::EndChild(); ImGui::Separator(); ImGui::SetNextItemWidth(300.0f * uiScale); - if (ImGui::SliderFloat("UI Scale", &tempScale, 0.25f, 3.0f)) { - // Dynamically changes UI scale + + static float sliderScale = 1.0f; + if (ImGui::IsWindowAppearing()) { + sliderScale = uiScale; } + ImGui::SliderFloat("UI Scale", &sliderScale, 0.25f, 3.0f); // Only update when user is not interacting with slider if (ImGui::IsItemDeactivatedAfterEdit()) { - uiScale = tempScale; - tempScale = uiScale; + uiScale = sliderScale; } + ImGui::SameLine(); // Align buttons right - float buttonsWidth = - ImGui::CalcTextSize("Settings (Under Construction)").x + ImGui::CalcTextSize("Exit").x + - ImGui::GetStyle().FramePadding.x * 4.0f + ImGui::GetStyle().ItemSpacing.x; + float buttonsWidth = ImGui::CalcTextSize("Settings").x + ImGui::CalcTextSize("Exit").x + + ImGui::GetStyle().FramePadding.x * 4.0f + + ImGui::GetStyle().ItemSpacing.x; ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - buttonsWidth); - if (ImGui::Button("Settings (Under Construction)")) { - // Todo + if (ImGui::Button("Settings")) { + showSettings = true; } + ImGui::SameLine(); if (ImGui::Button("Exit")) { @@ -214,6 +226,18 @@ void Launch() { ImGui::EndPopup(); } + if (showSettings) { + EmulatorSettings.SetBigPictureScale(static_cast(uiScale * 1000)); + EmulatorSettings.Save(); + DrawSettings(&showSettings); + + // update when settings dialog closed + if (!showSettings) { + uiScale = static_cast(EmulatorSettings.GetBigPictureScale() / 1000.f); + sliderScale = uiScale; + } + } + ImGui::PopFont(); ImGui::End(); ImGui::Render(); @@ -233,44 +257,56 @@ void Launch() { EmulatorSettings.SetBigPictureScale(static_cast(uiScale * 1000)); EmulatorSettings.Save(); - if (runGame) { + if (runEbootPath != "") { auto* emulator = Common::Singleton::Instance(); emulator->Run(runEbootPath); } } -void SetGameIcons() { +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; - // Use same line if content fits horizontally, move to next line if not - for (int i = 0; i < gameVec.size(); i++) { + for (int i = 0; i < games.size(); i++) { ImGui::BeginGroup(); - std::string ButtonName = "Button" + std::to_string(i); const char* ButtonNameChar = ButtonName.c_str(); - if (ImGui::ImageButton(ButtonNameChar, (ImTextureID)gameVec[i].iconTexture, + 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))) { - runGame = true; done = true; - runEbootPath = gameVec[i].ebootPath; + runEbootPath = games[i].ebootPath; + } + + if (popColor) { + ImGui::PopStyleColor(); } // Scroll to item only when newly-focused - if (ImGui::IsItemFocused() && !focusState[i]) { + if (ImGui::IsItemFocused() && !games[i].focusState) { ImGui::SetScrollHereY(0.5f); } - focusState[i] = ImGui::IsItemFocused(); + + if (ImGui::IsWindowFocused()) + games[i].focusState = ImGui::IsItemFocused(); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale); - ImGui::TextWrapped("%s", gameVec[i].title.c_str()); + 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); @@ -300,8 +336,16 @@ std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem:: return outputPath; } -void GetGameInfo() { - gameVec.clear(); +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)) { @@ -311,12 +355,6 @@ void GetGameInfo() { } Game game; - game.ebootPath = entry.path() / "eboot.bin"; - - const std::string iconFileName = "icon0.png"; - std::filesystem::path iconPath = UpdateChecker(iconFileName, entry.path()); - game.iconTexture = IMG_LoadTexture(renderer, iconPath.string().c_str()); - PSF psf; const std::string sfoFileName = "param.sfo"; std::filesystem::path sfoPath = UpdateChecker(sfoFileName, entry.path()); @@ -325,15 +363,63 @@ void GetGameInfo() { 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; } - gameVec.push_back(game); - focusState.push_back(false); + 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.h b/src/imgui/big_picture.h index 1d5b45f73..b5f035541 100644 --- a/src/imgui/big_picture.h +++ b/src/imgui/big_picture.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include namespace BigPictureMode { @@ -12,11 +12,17 @@ struct Game { SDL_Texture* iconTexture; std::filesystem::path ebootPath; std::string title; + std::string serial; + bool focusState; }; void Launch(); -void SetGameIcons(); -void GetGameInfo(); +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/settings_dialog_imgui.cpp b/src/imgui/settings_dialog_imgui.cpp new file mode 100644 index 000000000..f55a482a2 --- /dev/null +++ b/src/imgui/settings_dialog_imgui.cpp @@ -0,0 +1,602 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "core/devtools/layer.h" +#include "core/emulator_settings.h" +#include "imgui/imgui_std.h" +#include "settings_dialog_imgui.h" + +#include "imgui_fonts/notosansjp_regular.ttf.g.cpp" +#include "imgui_fonts/proggyvector_regular.ttf.g.cpp" + +CMRC_DECLARE(res); + +namespace BigPictureMode { + +const float gameImageSize = 200.f; +const float settingsIconSize = 125.f; +static std::vector settingsProfileVec = {}; + +static float uiScale = 1.0f; +static CurrentSettings currentSettings; +static Textures textures; +static SDL_Renderer* renderer; + +static SettingsCategory currentCategory = SettingsCategory::Profiles; +static std::string currentProfile = "Global"; +static bool closeOnSave = false; + +void Init() { + currentProfile = "Global"; + currentCategory = SettingsCategory::Profiles; + LoadSettings("Global"); + + SDL_Window* window = SDL_GetKeyboardFocus(); + renderer = SDL_GetRenderer(window); + + LoadEmbeddedTexture("src/images/big_picture/settings.png", textures.general); + LoadEmbeddedTexture("src/images/big_picture/folder.png", textures.profiles); + LoadEmbeddedTexture("src/images/big_picture/global-settings.png", textures.globalSettings); + LoadEmbeddedTexture("src/images/big_picture/experimental.png", textures.experimental); + LoadEmbeddedTexture("src/images/big_picture/graphics.png", textures.graphics); + LoadEmbeddedTexture("src/images/big_picture/controller.png", textures.input); + LoadEmbeddedTexture("src/images/big_picture/trophy.png", textures.trophy); + LoadEmbeddedTexture("src/images/big_picture/log.png", textures.log); + + GetGameInfo(settingsProfileVec, true, textures.globalSettings); + 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)) { + 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); + ImGui::BeginChild("Categories", ImVec2(0, 0), ImGuiChildFlags_AutoResizeY, + 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", textures.profiles, SettingsCategory::Profiles); + AddCategory("General", textures.general, SettingsCategory::General); + AddCategory("Graphics", textures.graphics, SettingsCategory::Graphics); + AddCategory("Input", textures.input, SettingsCategory::Input); + AddCategory("Trophy", textures.trophy, SettingsCategory::Trophy); + AddCategory("Log", textures.log, SettingsCategory::Log); + + if (currentProfile != "Global") + AddCategory("Experimental", textures.experimental, 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; + // choose next category with R1 + if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1)) { + int currentIndex = static_cast(currentCategory); + currentCategory == lastCategory + ? currentCategory = static_cast(0) + : currentCategory = static_cast(currentIndex + 1); + } + + // choose previous category with R1 + 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) { + 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)); + + if (category == SettingsCategory::General) { + if (ImGui::BeginTable("SettingsTable", 2)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale); + ImGui::TableSetupColumn("Value"); + + AddSettingCombo("Console Language", currentSettings.consoleLanguage); + AddSettingSliderInt("Volume", currentSettings.volume, 0, 500); + AddSettingBool("Show Splash Screen When Launching Game", currentSettings.showSplash); + AddSettingCombo("Audio Backend", currentSettings.audioBackend); + + 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", currentSettings.fullscreenMode); + AddSettingCombo("Present Mode", currentSettings.presentMode); + AddSettingSliderInt("Window Width", currentSettings.windowWidth, 0, 8000); + AddSettingSliderInt("Window Height", currentSettings.windowHeight, 0, 7000); + AddSettingBool("Enable HDR", currentSettings.hdrAllowed); + AddSettingBool("Enable FSR", currentSettings.fsrEnabled); + + if (currentSettings.fsrEnabled) { + AddSettingBool("Enable RCAS", currentSettings.rcasEnabled); + } + + if (currentSettings.rcasEnabled && currentSettings.fsrEnabled) { + AddSettingSliderFloat("RCAS Attenuation", currentSettings.rcasAttenuation, 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", currentSettings.motionControls); + AddSettingBool("Enable Background Controller Input", + currentSettings.backgroundController); + AddSettingCombo("Hide Cursor", currentSettings.cursorState); + + if (currentSettings.cursorState == 1) { + AddSettingSliderInt("Hide Cursor Idle Timeout", currentSettings.cursorTimeout, 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", currentSettings.trophyPopupDisabled); + if (!currentSettings.trophyPopupDisabled) { + AddSettingCombo("Trophy Notification Position", currentSettings.trophySide); + AddSettingSliderFloat("Trophy Notification Duration", + currentSettings.trophyDuration, 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"); + + AddSettingBool("Enable Logging", currentSettings.logEnabled); + if (currentSettings.logEnabled) { + AddSettingBool("Separate Log Files", currentSettings.separateLog); + AddSettingCombo("Log Type", currentSettings.logType); + } + + 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", currentSettings.extraDmem, 0, 20000); + AddSettingSliderInt("Vblank Frequency", currentSettings.vblankFrequency, 30, 360); + AddSettingCombo("Readbacks Mode", currentSettings.readbacksMode); + AddSettingBool("Enable Readback Linear Images", currentSettings.readbackLinearImages); + AddSettingBool("Enable Direct Memory Access", currentSettings.directMemoryAccess); + AddSettingBool("Enable Devkit Console Mode", currentSettings.devkitConsole); + AddSettingBool("Enable PS4 Neo Mode", currentSettings.neoMode); + AddSettingBool("Set PSN Sign-in to True", currentSettings.psnSignedIn); + AddSettingBool("Set Network Connected to True", currentSettings.connectedNetwork); + AddSettingBool("Enable Shader Cache", currentSettings.pipelineCacheEnabled); + + if (currentSettings.pipelineCacheEnabled) { + AddSettingBool("Compress Shader Cache to Zip File", + currentSettings.pipelineCacheArchive); + } + + ImGui::EndTable(); + } + } + + ImGui::PopStyleVar(); + + // Child Window if Needed + if (category == SettingsCategory::Profiles) { + ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened; + ImGui::BeginChild("ProfileSelect", ImVec2(0, 0), true, child_flags); + Overlay::TextCentered("Select Global or Game-Specific Settings Profile"); + SetProfileIcons(settingsProfileVec); + ImGui::EndChild(); + } +} + +void SaveSettings(std::string profile) { + const bool isSpecific = currentProfile != "Global"; + + /////////// General Tab + EmulatorSettings.SetConsoleLanguage( + languageMap.at(optionsLanguage.at(currentSettings.consoleLanguage)), isSpecific); + EmulatorSettings.SetVolumeSlider(currentSettings.volume, isSpecific); + EmulatorSettings.SetShowSplash(currentSettings.showSplash, isSpecific); + EmulatorSettings.SetAudioBackend(currentSettings.audioBackend, isSpecific); + + /////////// Graphics Tab + bool isFullscreen = currentSettings.fullscreenMode != 0; + EmulatorSettings.SetFullScreen(isFullscreen); + EmulatorSettings.SetFullScreenMode(optionsFullscreenMode.at(currentSettings.fullscreenMode), + isSpecific); + EmulatorSettings.SetPresentMode(optionsPresentMode.at(currentSettings.presentMode), isSpecific); + EmulatorSettings.SetWindowHeight(currentSettings.windowHeight, isSpecific); + EmulatorSettings.SetWindowWidth(currentSettings.windowWidth, isSpecific); + EmulatorSettings.SetHdrAllowed(currentSettings.hdrAllowed, isSpecific); + EmulatorSettings.SetFsrEnabled(currentSettings.fsrEnabled, isSpecific); + EmulatorSettings.SetRcasEnabled(currentSettings.rcasEnabled, isSpecific); + EmulatorSettings.SetRcasAttenuation(static_cast(currentSettings.rcasAttenuation * 1000), + isSpecific); + + /////////// Input Tab + EmulatorSettings.SetMotionControlsEnabled(currentSettings.motionControls, isSpecific); + EmulatorSettings.SetBackgroundControllerInput(currentSettings.backgroundController, isSpecific); + EmulatorSettings.SetCursorState(currentSettings.cursorState, isSpecific); + EmulatorSettings.SetCursorHideTimeout(currentSettings.cursorTimeout, isSpecific); + + /////////// Trophy Tab + EmulatorSettings.SetTrophyPopupDisabled(currentSettings.trophyPopupDisabled, isSpecific); + EmulatorSettings.SetTrophyNotificationSide(optionsTrophySide.at(currentSettings.trophySide), + isSpecific); + EmulatorSettings.SetTrophyNotificationDuration( + static_cast(currentSettings.trophyDuration)); + + /////////// Log Tab + EmulatorSettings.SetLogEnabled(currentSettings.logEnabled, isSpecific); + EmulatorSettings.SetLogType(optionsLogType.at(currentSettings.logType), isSpecific); + EmulatorSettings.SetSeparateLoggingEnabled(currentSettings.separateLog, isSpecific); + + /////////// Experimental Tab + if (isSpecific) { + EmulatorSettings.SetReadbacksMode(currentSettings.readbacksMode, true); + EmulatorSettings.SetReadbackLinearImagesEnabled(currentSettings.readbackLinearImages, true); + EmulatorSettings.SetDirectMemoryAccessEnabled(currentSettings.directMemoryAccess, true); + EmulatorSettings.SetDevKit(currentSettings.devkitConsole, true); + EmulatorSettings.SetNeo(currentSettings.neoMode, true); + EmulatorSettings.SetPSNSignedIn(currentSettings.psnSignedIn, true); + EmulatorSettings.SetConnectedToNetwork(currentSettings.connectedNetwork, true); + EmulatorSettings.SetPipelineCacheEnabled(currentSettings.pipelineCacheEnabled, true); + EmulatorSettings.SetPipelineCacheArchived(currentSettings.pipelineCacheArchive, true); + EmulatorSettings.SetExtraDmemInMBytes(currentSettings.extraDmem, true); + EmulatorSettings.SetVblankFrequency(currentSettings.vblankFrequency, 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; + } + } + currentSettings.consoleLanguage = GetComboIndex(language, optionsLanguage); + currentSettings.volume = EmulatorSettings.GetVolumeSlider(); + currentSettings.showSplash = EmulatorSettings.IsShowSplash(); + currentSettings.audioBackend = EmulatorSettings.GetAudioBackend(); + + /////////// Graphics Tab + currentSettings.fullscreenMode = + GetComboIndex(EmulatorSettings.GetFullScreenMode(), optionsFullscreenMode); + currentSettings.presentMode = + GetComboIndex(EmulatorSettings.GetPresentMode(), optionsPresentMode); + currentSettings.windowHeight = EmulatorSettings.GetWindowHeight(); + currentSettings.windowWidth = EmulatorSettings.GetWindowWidth(); + currentSettings.hdrAllowed = EmulatorSettings.IsHdrAllowed(); + currentSettings.fsrEnabled = EmulatorSettings.IsFsrEnabled(); + currentSettings.rcasEnabled = EmulatorSettings.IsRcasEnabled(); + currentSettings.rcasAttenuation = + static_cast(EmulatorSettings.GetRcasAttenuation() * 0.001f); + + /////////// Input Tab + currentSettings.motionControls = EmulatorSettings.IsMotionControlsEnabled(); + currentSettings.backgroundController = EmulatorSettings.IsBackgroundControllerInput(); + currentSettings.cursorState = EmulatorSettings.GetCursorState(); + currentSettings.cursorTimeout = EmulatorSettings.GetCursorHideTimeout(); + + /////////// Trophy Tab + currentSettings.trophyPopupDisabled = EmulatorSettings.IsTrophyPopupDisabled(); + currentSettings.trophySide = + GetComboIndex(EmulatorSettings.GetTrophyNotificationSide(), optionsTrophySide); + currentSettings.trophyDuration = + static_cast(EmulatorSettings.GetTrophyNotificationDuration()); + + /////////// Log Tab + currentSettings.logEnabled = EmulatorSettings.IsLogEnabled(); + currentSettings.logType = GetComboIndex(EmulatorSettings.GetLogType(), optionsLogType); + currentSettings.separateLog = EmulatorSettings.IsSeparateLoggingEnabled(); + + /////////// Experimental Tab + if (isSpecific) { + currentSettings.readbacksMode = EmulatorSettings.GetReadbacksMode(); + currentSettings.readbackLinearImages = EmulatorSettings.IsReadbackLinearImagesEnabled(); + currentSettings.directMemoryAccess = EmulatorSettings.IsDirectMemoryAccessEnabled(); + currentSettings.devkitConsole = EmulatorSettings.IsDevKit(); + currentSettings.neoMode = EmulatorSettings.IsNeo(); + currentSettings.psnSignedIn = EmulatorSettings.IsPSNSignedIn(); + currentSettings.connectedNetwork = EmulatorSettings.IsConnectedToNetwork(); + currentSettings.pipelineCacheEnabled = EmulatorSettings.IsPipelineCacheEnabled(); + currentSettings.pipelineCacheArchive = EmulatorSettings.IsPipelineCacheArchived(); + currentSettings.extraDmem = EmulatorSettings.GetExtraDmemInMBytes(); + currentSettings.vblankFrequency = 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::string label = "##" + name; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", name.c_str()); + + std::vector options = optionsMap.at(name); + 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; + } + } +} + +} // namespace BigPictureMode diff --git a/src/imgui/settings_dialog_imgui.h b/src/imgui/settings_dialog_imgui.h new file mode 100644 index 000000000..0686a8406 --- /dev/null +++ b/src/imgui/settings_dialog_imgui.h @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "big_picture.h" + +namespace BigPictureMode { + +enum class SettingsCategory { + Profiles, + General, + Graphics, + Input, + Trophy, + Log, + Experimental, +}; + +struct Textures { + SDL_Texture* profiles; + SDL_Texture* general; + SDL_Texture* globalSettings; + SDL_Texture* experimental; + SDL_Texture* graphics; + SDL_Texture* input; + SDL_Texture* trophy; + SDL_Texture* log; +}; + +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 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); +int GetComboIndex(std::string selection, std::vector options); + +//////////////////// Settings struct +// Note: use int instead of std::string for all combo settings as needed by ImGui +// then convert to string when saving/loading + +struct CurrentSettings { + // General tab + int consoleLanguage; + int volume; + bool showSplash; + int audioBackend; + + // Graphics tab + int fullscreenMode; + int presentMode; + int windowWidth; + int windowHeight; + bool hdrAllowed; + bool fsrEnabled; + bool rcasEnabled; + float rcasAttenuation; + + // Input tab + bool motionControls; + bool backgroundController; + int cursorState; + int cursorTimeout; + + // Trophy tab + bool trophyPopupDisabled; + int trophySide; + float trophyDuration; + + // Log tab + bool logEnabled; + bool separateLog; + int logType; + + // Experimental tab + int readbacksMode; + bool readbackLinearImages; + bool directMemoryAccess; + bool devkitConsole; + bool neoMode; + bool psnSignedIn; + bool connectedNetwork; + bool pipelineCacheEnabled; + bool pipelineCacheArchive; + int extraDmem; + int vblankFrequency; +}; + +//////////////////// option maps for comboboxes and other needed constants +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}}; + +const std::vector optionsLanguage = {"Arabic", + "Czech", + "Danish", + "Dutch", + "English (United Kingdom)", + "English (United States)", + "Finnish", + "French (Canada)", + "French (France)", + "German", + "Greek", + "Hungarian", + "Indonesian", + "Italian", + "Japanese", + "Korean", + "Norwegian (Bokmaal)", + "Polish", + "Portuguese (Brazil)", + "Portuguese (Portugal)", + "Romanian", + "Russian", + "Simplified Chinese", + "Spanish (Latin America)", + "Spanish (Spain)", + "Swedish", + "Thai", + "Traditional Chinese", + "Turkish", + "Ukrainian", + "Vietnamese"}; + +const std::vector optionsLogType = {"sync", "async"}; +const std::vector optionsFullscreenMode = {"Windowed", "Fullscreen", + "Fullscreen (Borderless)"}; +const std::vector optionsAudioBackend = {"SDL", "OpenAL"}; +const std::vector optionsPresentMode = {"Mailbox", "Fifo", "Immediate"}; +const std::vector optionsHideCursor = {"Never", "Idle", "Always"}; +const std::vector optionsTrophySide = {"left", "right", "top", "bottom"}; +const std::vector optionsReadbacksMode = {"Disabled", "Relaxed", "Precise"}; + +const std::map> optionsMap = { + {"Log Type", optionsLogType}, + {"Console Language", optionsLanguage}, + {"Audio Backend", optionsAudioBackend}, + {"Display Mode", optionsFullscreenMode}, + {"Present Mode", optionsPresentMode}, + {"Hide Cursor", optionsHideCursor}, + {"Trophy Notification Position", optionsTrophySide}, + {"Readbacks Mode", optionsReadbacksMode}, +}; + +} // namespace BigPictureMode