Big Picture: add settings configuration (#4255)
* prototype settings look * put categories on top instead of on the side, plays nicer with input system * added profiles + saving and loading framework * add helper functions * fix autofocusing * add code for using embedded image files * put settings in different files * clang * remove sdl_image submodule, unify functions when possible * Always display selected profile in settings content areas * Fix focusing, logs, highlight profile text * persist highlight on focused buttons, add all categories placeholders * All settings added * navigate tabs with l1/r1
3
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
23
externals/CMakeLists.txt
vendored
@ -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)
|
||||
|
||||
1
externals/sdl3_image
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 1aedddcbd205c4e1ea0f99fdb2c785acc8e2489b
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
BIN
src/images/big_picture/controller.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/images/big_picture/experimental.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/images/big_picture/folder.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/images/big_picture/global-settings.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/images/big_picture/graphics.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
src/images/big_picture/log.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/images/big_picture/settings.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/images/big_picture/trophy.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
@ -1,35 +1,38 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <fstream>
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <imgui.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#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<Game> gameVec = {};
|
||||
static std::vector<bool> 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<float>(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<int>(uiScale * 1000));
|
||||
EmulatorSettings.Save();
|
||||
DrawSettings(&showSettings);
|
||||
|
||||
// update when settings dialog closed
|
||||
if (!showSettings) {
|
||||
uiScale = static_cast<float>(EmulatorSettings.GetBigPictureScale() / 1000.f);
|
||||
sliderScale = uiScale;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
ImGui::Render();
|
||||
@ -233,44 +257,56 @@ void Launch() {
|
||||
EmulatorSettings.SetBigPictureScale(static_cast<int>(uiScale * 1000));
|
||||
EmulatorSettings.Save();
|
||||
|
||||
if (runGame) {
|
||||
if (runEbootPath != "") {
|
||||
auto* emulator = Common::Singleton<Core::Emulator>::Instance();
|
||||
emulator->Run(runEbootPath);
|
||||
}
|
||||
}
|
||||
|
||||
void SetGameIcons() {
|
||||
void SetGameIcons(std::vector<Game>& 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<Game>& 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<char> data =
|
||||
std::vector<char>(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
LoadTextureData(data, texture, m_renderer);
|
||||
}
|
||||
|
||||
void LoadTextureData(std::vector<char> 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
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
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<Game>& games);
|
||||
void GetGameInfo(std::vector<Game>& 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<char> data, SDL_Texture*& texture, SDL_Renderer* renderer);
|
||||
|
||||
} // namespace BigPictureMode
|
||||
|
||||
602
src/imgui/settings_dialog_imgui.cpp
Normal file
@ -0,0 +1,602 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <stb_image.h>
|
||||
|
||||
#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<Game> 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<float>(EmulatorSettings.GetBigPictureScale() / 1000.f);
|
||||
}
|
||||
|
||||
void DeInit() {
|
||||
EmulatorSettings.Load();
|
||||
EmulatorSettings.SetBigPictureScale(static_cast<int>(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<int>(currentCategory);
|
||||
currentCategory == lastCategory
|
||||
? currentCategory = static_cast<SettingsCategory>(0)
|
||||
: currentCategory = static_cast<SettingsCategory>(currentIndex + 1);
|
||||
}
|
||||
|
||||
// choose previous category with R1
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) {
|
||||
int currentIndex = static_cast<int>(currentCategory);
|
||||
currentIndex == 0 ? currentCategory = lastCategory
|
||||
: currentCategory = static_cast<SettingsCategory>(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<int>(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<double>(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<float>(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<float>(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<std::string> 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<std::string> 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<char> texData = std::vector<char>(file.begin(), file.end());
|
||||
|
||||
BigPictureMode::LoadTextureData(texData, texture, renderer);
|
||||
}
|
||||
|
||||
void SetProfileIcons(std::vector<Game>& 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
|
||||
191
src/imgui/settings_dialog_imgui.h
Normal file
@ -0,0 +1,191 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#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<Game>& 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<std::string> 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<std::string, int> 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<std::string> 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<std::string> optionsLogType = {"sync", "async"};
|
||||
const std::vector<std::string> optionsFullscreenMode = {"Windowed", "Fullscreen",
|
||||
"Fullscreen (Borderless)"};
|
||||
const std::vector<std::string> optionsAudioBackend = {"SDL", "OpenAL"};
|
||||
const std::vector<std::string> optionsPresentMode = {"Mailbox", "Fifo", "Immediate"};
|
||||
const std::vector<std::string> optionsHideCursor = {"Never", "Idle", "Always"};
|
||||
const std::vector<std::string> optionsTrophySide = {"left", "right", "top", "bottom"};
|
||||
const std::vector<std::string> optionsReadbacksMode = {"Disabled", "Relaxed", "Precise"};
|
||||
|
||||
const std::map<std::string, std::vector<std::string>> 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
|
||||