Imgui: make imgui emulator settings accessible with hotkey (#4345)

* put all big picture files in same folder

* Fix includes

* Update main.cpp

* make imgui settings dialog accessible by hotkey
This commit is contained in:
rainmakerv2 2026-05-04 18:51:08 +08:00 committed by GitHub
parent ea8bed1c94
commit ad102a173a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1307 additions and 1093 deletions

View File

@ -1096,10 +1096,6 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/renderer/imgui_core.h
src/imgui/renderer/imgui_impl_sdl3.cpp
src/imgui/renderer/imgui_impl_sdl3.h
src/imgui/renderer/imgui_impl_sdl3_bpm.cpp
src/imgui/renderer/imgui_impl_sdl3_bpm.h
src/imgui/renderer/imgui_impl_sdlrenderer3.cpp
src/imgui/renderer/imgui_impl_sdlrenderer3.h
src/imgui/renderer/imgui_impl_vulkan.cpp
src/imgui/renderer/imgui_impl_vulkan.h
src/imgui/renderer/font_data.cpp
@ -1108,10 +1104,16 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/renderer/font_stack.h
src/imgui/renderer/texture_manager.cpp
src/imgui/renderer/texture_manager.h
src/imgui/big_picture.cpp
src/imgui/big_picture.h
src/imgui/settings_dialog_imgui.cpp
src/imgui/settings_dialog_imgui.h
src/imgui/big_picture/big_picture.cpp
src/imgui/big_picture/big_picture.h
src/imgui/big_picture/settings_dialog_imgui.cpp
src/imgui/big_picture/settings_dialog_imgui.h
src/imgui/big_picture/settings_dialog_layer.cpp
src/imgui/big_picture/settings_dialog_layer.h
src/imgui/big_picture/imgui_impl_sdl3_big_picture.cpp
src/imgui/big_picture/imgui_impl_sdl3_big_picture.h
src/imgui/big_picture/imgui_impl_sdlrenderer3.cpp
src/imgui/big_picture/imgui_impl_sdlrenderer3.h
)
set(INPUT src/input/controller.cpp
@ -1274,7 +1276,6 @@ cmrc_add_resource_library(embedded-resources
NAMESPACE res
src/images/big_picture/folder.png
src/images/big_picture/settings.png
src/images/big_picture/global-settings.png
src/images/big_picture/experimental.png
src/images/big_picture/graphics.png
src/images/big_picture/controller.png

View File

@ -79,7 +79,6 @@ path = [
"src/images/big_picture/controller.png",
"src/images/big_picture/experimental.png",
"src/images/big_picture/folder.png",
"src/images/big_picture/global-settings.png",
"src/images/big_picture/graphics.png",
"src/images/big_picture/log.png",
"src/images/big_picture/settings.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,28 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <SDL3/SDL.h>
namespace BigPictureMode {
struct Game {
SDL_Texture* iconTexture;
std::filesystem::path ebootPath;
std::string title;
std::string serial;
bool focusState;
};
void Launch(char* executableName);
void SetGameIcons(std::vector<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

View File

@ -2,8 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <cmrc/cmrc.hpp>
#include <imgui.h>
#include <stb_image.h>
#include "big_picture.h"
@ -14,24 +12,181 @@
#include "emulator.h"
#include "imgui/imgui_std.h"
#include "imgui/renderer/font_stack.h"
#include "imgui/renderer/imgui_impl_sdl3_bpm.h"
#include "imgui/renderer/imgui_impl_sdlrenderer3.h"
#include "imgui_impl_sdl3_big_picture.h"
#include "imgui_impl_sdlrenderer3.h"
#include "settings_dialog_imgui.h"
CMRC_DECLARE(res);
namespace BigPictureMode {
const float gameImageSize = 200.f;
constexpr float gameImageSize = 200.f;
static bool done = false;
static bool showSettings = false;
bool done = false;
bool showSettings = false;
static std::filesystem::path runEbootPath = "";
static std::vector<Game> gameVec = {};
std::filesystem::path runEbootPath = "";
std::vector<IconInfo> gameIcons = {};
static float uiScale = 1.0f;
static SDL_Renderer* renderer;
float uiScale = 1.0f;
SDL_Renderer* renderer;
namespace {
std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem::path game_folder) {
std::filesystem::path outputPath;
auto update_folder = game_folder;
update_folder += "-UPDATE";
auto patch_folder = game_folder;
patch_folder += "-patch";
if (std::filesystem::exists(update_folder / "sce_sys" / sceItem)) {
outputPath = update_folder / "sce_sys" / sceItem;
} else if (std::filesystem::exists(patch_folder / "sce_sys" / sceItem)) {
outputPath = patch_folder / "sce_sys" / sceItem;
} else {
outputPath = game_folder / "sce_sys" / sceItem;
}
return outputPath;
}
void SetGameIcons(std::vector<IconInfo>& gameIcons) {
ImGuiStyle& style = ImGui::GetStyle();
const float maxAvailableWidth = ImGui::GetContentRegionAvail().x;
const float itemSpacing = style.ItemSpacing.x; // already scaled
const float padding = 10.0f * uiScale;
float rowContentWidth = gameImageSize * uiScale + itemSpacing;
for (int i = 0; i < gameIcons.size(); i++) {
ImGui::BeginGroup();
std::string ButtonName = "Button" + std::to_string(i);
const char* ButtonNameChar = ButtonName.c_str();
bool buttonFocused = (ImGui::GetID(ButtonNameChar) == ImGui::GetFocusID());
if (buttonFocused) {
ImGui::PushStyleColor(ImGuiCol_Button,
ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]);
}
ImTextureID id = gameIcons[i].textureId;
if (id != nullptr) {
if (ImGui::ImageButton(ButtonNameChar, id,
ImVec2(gameImageSize * uiScale, gameImageSize * uiScale))) {
done = true;
runEbootPath = gameIcons[i].ebootPath;
}
}
if (buttonFocused) {
ImGui::PopStyleColor();
}
// Scroll to item only when newly-focused
if (ImGui::IsItemFocused() && !gameIcons[i].focusState) {
ImGui::SetScrollHereY(0.5f);
}
if (ImGui::IsWindowFocused()) {
gameIcons[i].focusState = ImGui::IsItemFocused();
}
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale);
ImGui::TextWrapped("%s", gameIcons[i].title.c_str());
ImGui::PopTextWrapPos();
ImGui::EndGroup();
// Use same line if content fits horizontally, move to next line if not
rowContentWidth += (gameImageSize * uiScale + itemSpacing * 2 + padding);
if (rowContentWidth < maxAvailableWidth) {
ImGui::SameLine(0.0f, padding);
} else {
ImGui::Dummy(ImVec2(0.0f, padding));
rowContentWidth = gameImageSize * uiScale + itemSpacing;
}
}
}
} // namespace
SDL_Texture* LoadSdlTextureData(std::vector<u8> data) {
int image_width = 0;
int image_height = 0;
int channels = 4;
unsigned char* image_data = stbi_load_from_memory(
(const unsigned char*)data.data(), (int)data.size(), &image_width, &image_height, NULL, 4);
if (image_data == nullptr) {
LOG_ERROR(ImGui, "Failed to load image: {}", stbi_failure_reason());
}
SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32,
(void*)image_data, channels * image_width);
if (surface == nullptr) {
LOG_ERROR(ImGui, "Unable to create SDL surface: {}", SDL_GetError());
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr) {
LOG_ERROR(ImGui, "Unable to create SDL texture: {}", SDL_GetError());
}
SDL_DestroySurface(surface);
stbi_image_free(image_data);
return texture;
}
SDL_Texture* LoadSdlTextureDataFromFile(std::filesystem::path filePath) {
std::ifstream file(filePath, std::ios::binary);
std::vector<u8> data =
std::vector<u8>(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
return LoadSdlTextureData(data);
}
void GetGameIconInfo(std::vector<IconInfo>& icons) {
icons.clear();
for (const auto& installLoc : EmulatorSettings.GetAllGameInstallDirs()) {
if (installLoc.enabled && std::filesystem::exists(installLoc.path)) {
for (const auto& entry : std::filesystem::directory_iterator(installLoc.path)) {
if (entry.path().filename().string().ends_with("-UPDATE") ||
entry.path().filename().string().ends_with("-patch") || !entry.is_directory()) {
continue;
}
IconInfo icon;
PSF psf;
const std::string sfoFileName = "param.sfo";
std::filesystem::path sfoPath = UpdateChecker(sfoFileName, entry.path());
if (std::filesystem::exists(sfoPath) && psf.Open(sfoPath)) {
if (const auto title = psf.GetString("TITLE"); title.has_value()) {
icon.title = *title;
}
if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) {
icon.serial = *title_id;
}
} else {
continue;
}
const std::string iconFileName = "icon0.png";
std::filesystem::path iconPath = UpdateChecker(iconFileName, entry.path());
SDL_Texture* texture = LoadSdlTextureDataFromFile(iconPath);
icon.textureId = ImTextureID(texture);
icon.ebootPath = entry.path() / "eboot.bin";
icon.focusState = false;
icons.push_back(icon);
}
}
}
std::sort(icons.begin(), icons.end(), [](const IconInfo& a, const IconInfo& b) {
return a.title < b.title; // Alphabetical order
});
}
void Launch(char* executableName) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
@ -42,15 +197,12 @@ void Launch(char* executableName) {
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
LOG_ERROR(ImGui, "SDL_INIT_GAMEPAD Error: {}", SDL_GetError());
SDL_Quit();
return;
}
SDL_Window* window =
SDL_CreateWindow("shadPS4 Big Picture Mode", 1280, 720, SDL_WINDOW_FULLSCREEN);
renderer = SDL_CreateRenderer(window, nullptr);
// Check if window creation failed
if (window == nullptr) {
LOG_ERROR(ImGui, "SDL Window Creation Error: {}", SDL_GetError());
SDL_DestroyRenderer(renderer);
@ -66,38 +218,21 @@ void Launch(char* executableName) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigNavCursorVisibleAlways = true;
ImFontConfig font_cfg;
font_cfg.OversampleH = 2;
font_cfg.OversampleV = 1;
ImFont* myFont = ImGui::FontStack::AddPrimaryUiFont(
io.Fonts, 32.0f, EmulatorSettings.GetConsoleLanguage(), font_cfg, true);
io.FontDefault = myFont;
ImFontConfig cfgBase;
cfgBase.OversampleH = 2;
cfgBase.OversampleV = 1;
io.FontDefault = ImGui::FontStack::AddPrimaryUiFont(
io.Fonts, 64.0f, EmulatorSettings.GetConsoleLanguage(), cfgBase, true);
io.FontGlobalScale = 0.5f;
io.Fonts->Build();
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f); // black
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.40f, 0.70f, 1.00f); // blue
colors[ImGuiCol_HeaderHovered] = ImVec4(0.25f, 0.50f, 0.85f, 1.00f); // lighter blue
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); // another light blue
colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); // another light blue
style.WindowRounding = 0.0f;
style.FrameRounding = 5.0f * uiScale;
style.ItemSpacing = ImVec2(10.0f * uiScale, 10.0f * uiScale);
style.FramePadding = ImVec2(10.0f * uiScale, 10.0f * uiScale);
style.FrameBorderSize = 2.5f * uiScale;
style.WindowBorderSize = 0.0f;
style.WindowPadding = ImVec2(20.0f * uiScale, 20.0f * uiScale);
style.GrabMinSize = 20.0f * uiScale;
ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer3_Init(renderer);
GetGameInfo(gameVec, false);
GetGameIconInfo(gameIcons);
uiScale = static_cast<float>(EmulatorSettings.GetBigPictureScale() / 1000.f);
ImGuiEmuSettings::SettingsWindow settingsWindow(false);
while (!done) {
SDL_Event event;
@ -111,7 +246,24 @@ void Launch(char* executableName) {
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGui::PushFont(myFont);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.06f, 0.06f, 0.06f, 1.00f)); // black
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.40f, 0.70f, 1.00f)); // blue
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
ImVec4(0.25f, 0.50f, 0.85f, 1.00f)); // lighter blue
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive,
ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue
ImGui::PushStyleColor(ImGuiCol_SliderGrab,
ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.0f * uiScale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f * uiScale, 10.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f * uiScale, 10.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.5f * uiScale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f * uiScale, 20.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 20.0f * uiScale);
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
@ -139,7 +291,7 @@ void Launch(char* executableName) {
ImGui::SetKeyboardFocusHere();
}
SetGameIcons(gameVec);
SetGameIcons(gameIcons);
ImGui::EndChild();
ImGui::Separator();
@ -200,17 +352,18 @@ void Launch(char* executableName) {
if (showSettings) {
EmulatorSettings.SetBigPictureScale(static_cast<int>(uiScale * 1000));
EmulatorSettings.Save();
DrawSettings(&showSettings);
settingsWindow.DrawSettings(&showSettings);
// update when settings dialog closed
if (!showSettings) {
uiScale = static_cast<float>(EmulatorSettings.GetBigPictureScale() / 1000.f);
sliderScale = uiScale;
GetGameInfo(gameVec, false);
GetGameIconInfo(gameIcons);
}
}
ImGui::PopFont();
ImGui::PopStyleVar(8);
ImGui::PopStyleColor(5);
ImGui::End();
ImGui::Render();
SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
@ -236,163 +389,4 @@ void Launch(char* executableName) {
}
}
void SetGameIcons(std::vector<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))) {
done = true;
runEbootPath = games[i].ebootPath;
}
if (popColor) {
ImGui::PopStyleColor();
}
// Scroll to item only when newly-focused
if (ImGui::IsItemFocused() && !games[i].focusState) {
ImGui::SetScrollHereY(0.5f);
}
if (ImGui::IsWindowFocused())
games[i].focusState = ImGui::IsItemFocused();
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + gameImageSize * uiScale);
ImGui::TextWrapped("%s", games[i].title.c_str());
ImGui::PopTextWrapPos();
ImGui::EndGroup();
// Use same line if content fits horizontally, move to next line if not
rowContentWidth += (gameImageSize * uiScale + itemSpacing * 2 + padding);
if (rowContentWidth < maxAvailableWidth) {
ImGui::SameLine(0.0f, padding);
} else {
ImGui::Dummy(ImVec2(0.0f, padding));
rowContentWidth = gameImageSize * uiScale + itemSpacing;
}
}
}
std::filesystem::path UpdateChecker(const std::string sceItem, std::filesystem::path game_folder) {
std::filesystem::path outputPath;
auto update_folder = game_folder;
update_folder += "-UPDATE";
auto patch_folder = game_folder;
patch_folder += "-patch";
if (std::filesystem::exists(update_folder / "sce_sys" / sceItem)) {
outputPath = update_folder / "sce_sys" / sceItem;
} else if (std::filesystem::exists(patch_folder / "sce_sys" / sceItem)) {
outputPath = patch_folder / "sce_sys" / sceItem;
} else {
outputPath = game_folder / "sce_sys" / sceItem;
}
return outputPath;
}
void GetGameInfo(std::vector<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)) {
if (entry.path().filename().string().ends_with("-UPDATE") ||
entry.path().filename().string().ends_with("-patch") || !entry.is_directory()) {
continue;
}
Game game;
PSF psf;
const std::string sfoFileName = "param.sfo";
std::filesystem::path sfoPath = UpdateChecker(sfoFileName, entry.path());
if (psf.Open(sfoPath)) {
if (const auto title = psf.GetString("TITLE"); title.has_value()) {
game.title = *title;
}
if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) {
game.serial = *title_id;
}
} else {
continue;
}
const std::string iconFileName = "icon0.png";
std::filesystem::path iconPath = UpdateChecker(iconFileName, entry.path());
LoadTextureDataFromFile(iconPath, game.iconTexture, renderer);
game.ebootPath = entry.path() / "eboot.bin";
game.focusState = false;
games.push_back(game);
}
}
}
// Keep global settings at the start if it's added
auto start = AddGlobalSettings ? games.begin() + 1 : (games.begin());
std::sort(start, games.end(), [](const Game& a, const Game& b) {
return a.title < b.title; // Alphabetical order
});
}
void LoadTextureDataFromFile(std::filesystem::path filePath, SDL_Texture*& texture,
SDL_Renderer* m_renderer) {
std::ifstream file(filePath, std::ios::binary);
std::vector<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

View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
#include <SDL3/SDL.h>
#include <imgui.h>
#include "common/types.h"
namespace BigPictureMode {
struct IconInfo {
ImTextureID textureId;
std::filesystem::path ebootPath;
std::string title;
std::string serial;
bool focusState;
};
void Launch(char* executableName);
void GetGameIconInfo(std::vector<IconInfo>& icons);
SDL_Texture* LoadSdlTextureData(std::vector<u8> data);
SDL_Texture* LoadSdlTextureDataFromFile(std::filesystem::path filePath);
} // namespace BigPictureMode

View File

@ -5,7 +5,7 @@
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_sdl3_bpm.h"
#include "imgui_impl_sdl3_big_picture.h"
// Clang warnings with -Weverything
#if defined(__clang__)

View File

@ -0,0 +1,813 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <ranges>
#include <ImGuiFileDialog.h>
#include <cmrc/cmrc.hpp>
#include <stb_image.h>
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "core/devtools/layer.h"
#include "imgui/imgui_std.h"
#include "settings_dialog_imgui.h"
CMRC_DECLARE(res);
constexpr float gameImageSize = 200.f;
constexpr float settingsIconSize = 125.f;
namespace ImGuiEmuSettings {
int SettingsWindow::GetComboIndex(std::string selection, std::vector<std::string> options) {
for (int i = 0; i < options.size(); i++) {
if (selection == options[i])
return i;
}
return 0;
}
void SettingsWindow::LoadSettings(std::string profile) {
const bool isSpecific = currentProfile != "Global";
isSpecific ? EmulatorSettings.Load(profile) : EmulatorSettings.Load();
/////////// General Tab
int languageIndex = EmulatorSettings.GetConsoleLanguage();
std::string language;
for (const auto& [key, value] : languageMap) {
if (value == languageIndex) {
language = key;
}
}
consoleLanguageSetting = GetComboIndex(language, languageOptions);
volumeSetting = EmulatorSettings.GetVolumeSlider();
showSplashSetting = EmulatorSettings.IsShowSplash();
audioBackendSetting = EmulatorSettings.GetAudioBackend();
/////////// Graphics Tab
fullscreenModeSetting =
GetComboIndex(EmulatorSettings.GetFullScreenMode(), fullscreenModeOptions);
presentModeSetting = GetComboIndex(EmulatorSettings.GetPresentMode(), presentModeOptions);
windowHeightSetting = EmulatorSettings.GetWindowHeight();
windowWidthSetting = EmulatorSettings.GetWindowWidth();
hdrAllowedSetting = EmulatorSettings.IsHdrAllowed();
fsrEnabledSetting = EmulatorSettings.IsFsrEnabled();
rcasEnabledSetting = EmulatorSettings.IsRcasEnabled();
rcasAttenuationSetting = static_cast<float>(EmulatorSettings.GetRcasAttenuation() * 0.001f);
/////////// Input Tab
motionControlsSetting = EmulatorSettings.IsMotionControlsEnabled();
backgroundControllerSetting = EmulatorSettings.IsBackgroundControllerInput();
cursorStateSetting = EmulatorSettings.GetCursorState();
cursorTimeoutSetting = EmulatorSettings.GetCursorHideTimeout();
/////////// Trophy Tab
trophyPopupDisabledSetting = EmulatorSettings.IsTrophyPopupDisabled();
trophySideSetting =
GetComboIndex(EmulatorSettings.GetTrophyNotificationSide(), trophySideOptions);
trophyDurationSetting = static_cast<float>(EmulatorSettings.GetTrophyNotificationDuration());
/////////// Log Tab
logEnableSetting = EmulatorSettings.IsLogEnable();
logSeparateSetting = EmulatorSettings.IsLogSeparate();
logSyncSetting = EmulatorSettings.IsLogSync();
/////////// Experimental Tab
if (isSpecific) {
readbacksModeSetting = EmulatorSettings.GetReadbacksMode();
readbackLinearImagesSetting = EmulatorSettings.IsReadbackLinearImagesEnabled();
directMemoryAccessSetting = EmulatorSettings.IsDirectMemoryAccessEnabled();
devkitConsoleSetting = EmulatorSettings.IsDevKit();
neoModeSetting = EmulatorSettings.IsNeo();
shadnetEnabledSetting = EmulatorSettings.IsShadNetEnabled();
connectedNetworkSetting = EmulatorSettings.IsConnectedToNetwork();
pipelineCacheEnabledSetting = EmulatorSettings.IsPipelineCacheEnabled();
pipelineCacheArchiveSetting = EmulatorSettings.IsPipelineCacheArchived();
extraDmemSetting = EmulatorSettings.GetExtraDmemInMBytes();
vblankFrequencySetting = EmulatorSettings.GetVblankFrequency();
}
}
void SettingsWindow::SaveSettings(std::string profile) {
const bool isSpecific = currentProfile != "Global";
/////////// General Tab
EmulatorSettings.SetConsoleLanguage(languageMap.at(languageOptions.at(consoleLanguageSetting)),
isSpecific);
EmulatorSettings.SetVolumeSlider(volumeSetting, isSpecific);
EmulatorSettings.SetShowSplash(showSplashSetting, isSpecific);
EmulatorSettings.SetAudioBackend(audioBackendSetting, isSpecific);
/////////// Graphics Tab
bool isFullscreen = fullscreenModeSetting != 0;
EmulatorSettings.SetFullScreen(isFullscreen);
EmulatorSettings.SetFullScreenMode(fullscreenModeOptions.at(fullscreenModeSetting), isSpecific);
EmulatorSettings.SetPresentMode(presentModeOptions.at(presentModeSetting), isSpecific);
EmulatorSettings.SetWindowHeight(windowHeightSetting, isSpecific);
EmulatorSettings.SetWindowWidth(windowWidthSetting, isSpecific);
EmulatorSettings.SetHdrAllowed(hdrAllowedSetting, isSpecific);
EmulatorSettings.SetFsrEnabled(fsrEnabledSetting, isSpecific);
EmulatorSettings.SetRcasEnabled(rcasEnabledSetting, isSpecific);
EmulatorSettings.SetRcasAttenuation(static_cast<int>(rcasAttenuationSetting * 1000),
isSpecific);
/////////// Input Tab
EmulatorSettings.SetMotionControlsEnabled(motionControlsSetting, isSpecific);
EmulatorSettings.SetBackgroundControllerInput(backgroundControllerSetting, isSpecific);
EmulatorSettings.SetCursorState(cursorStateSetting, isSpecific);
EmulatorSettings.SetCursorHideTimeout(cursorTimeoutSetting, isSpecific);
/////////// Trophy Tab
EmulatorSettings.SetTrophyPopupDisabled(trophyPopupDisabledSetting, isSpecific);
EmulatorSettings.SetTrophyNotificationSide(trophySideOptions.at(trophySideSetting), isSpecific);
EmulatorSettings.SetTrophyNotificationDuration(static_cast<double>(trophyDurationSetting));
/////////// Log Tab
EmulatorSettings.SetLogEnable(logEnableSetting, isSpecific);
EmulatorSettings.SetLogSeparate(logSeparateSetting, isSpecific);
EmulatorSettings.SetLogSync(logSyncSetting, isSpecific);
/////////// Experimental Tab
if (isSpecific) {
EmulatorSettings.SetReadbacksMode(readbacksModeSetting, true);
EmulatorSettings.SetReadbackLinearImagesEnabled(readbackLinearImagesSetting, true);
EmulatorSettings.SetDirectMemoryAccessEnabled(directMemoryAccessSetting, true);
EmulatorSettings.SetDevKit(devkitConsoleSetting, true);
EmulatorSettings.SetNeo(neoModeSetting, true);
EmulatorSettings.SetShadNetEnabled(shadnetEnabledSetting, true);
EmulatorSettings.SetConnectedToNetwork(connectedNetworkSetting, true);
EmulatorSettings.SetPipelineCacheEnabled(pipelineCacheEnabledSetting, true);
EmulatorSettings.SetPipelineCacheArchived(pipelineCacheArchiveSetting, true);
EmulatorSettings.SetExtraDmemInMBytes(extraDmemSetting, true);
EmulatorSettings.SetVblankFrequency(vblankFrequencySetting, true);
}
isSpecific ? EmulatorSettings.Save(profile) : EmulatorSettings.Save();
}
void SettingsWindow::SaveInstallDirs() {
std::string profile;
const bool isGlobal = currentProfile == "Global";
if (!isGlobal) {
profile = currentProfile.substr(0, 9);
EmulatorSettings.Load();
}
EmulatorSettings.SetAllGameInstallDirs(m_GameInstallDirs);
EmulatorSettings.Save();
if (!isGlobal) {
EmulatorSettings.Load(profile);
}
if (!isGameRunning) {
GetProfileInfo();
}
}
void SettingsWindow::GetProfileInfo() {
GetGameIconInfo(profileIcons);
BigPictureMode::IconInfo global;
global.title = "Global";
profileIcons.emplace(profileIcons.begin(), global);
}
SettingsWindow::SettingsWindow(bool gameRunning) : isGameRunning(gameRunning) {
auto resource = cmrc::res::get_filesystem();
auto loadTexture = [&](const std::string& resourcePath,
std::variant<SDL_Texture*, ImGui::RefCountedTexture>& texture) {
auto file = resource.open(resourcePath);
std::vector<u8> texData = std::vector<u8>(file.begin(), file.end());
gameRunning ? texture = ImGui::RefCountedTexture::DecodePngTexture(texData)
: texture = BigPictureMode::LoadSdlTextureData(texData);
};
loadTexture("src/images/big_picture/settings.png", generalTexture);
loadTexture("src/images/big_picture/experimental.png", experimentalTexture);
loadTexture("src/images/big_picture/graphics.png", graphicsTexture);
loadTexture("src/images/big_picture/controller.png", inputTexture);
loadTexture("src/images/big_picture/trophy.png", trophyTexture);
loadTexture("src/images/big_picture/log.png", logTexture);
loadTexture("src/images/big_picture/folder.png", foldersTexture);
loadTexture("src/images/big_picture/profiles.png", profilesTexture);
auto languageKeys = std::views::keys(languageMap);
languageOptions.assign(languageKeys.begin(), languageKeys.end());
currentProfile = "Global";
m_GameInstallDirs = EmulatorSettings.GetAllGameInstallDirs();
currentCategory = isGameRunning ? SettingsCategory::General : SettingsCategory::Profiles;
uiScale = static_cast<float>(EmulatorSettings.GetBigPictureScale() / 1000.f);
bool customConfigFound = false;
if (isGameRunning) {
runningGameSerial = std::string(Common::ElfInfo::Instance().GameSerial());
std::filesystem::path customConfigFile =
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
(runningGameSerial + ".json");
if (std::filesystem::exists(customConfigFile)) {
customConfigFound = true;
currentProfile =
runningGameSerial + " - " + std::string(Common::ElfInfo::Instance().Title());
}
} else {
GetProfileInfo();
}
customConfigFound ? LoadSettings(runningGameSerial) : LoadSettings("Global");
}
void SettingsWindow::DeInit() {
EmulatorSettings.Load();
EmulatorSettings.SetBigPictureScale(static_cast<int>(uiScale * 1000));
EmulatorSettings.Save();
if (isGameRunning && !runningGameSerial.empty()) {
EmulatorSettings.Load(runningGameSerial);
}
}
void SettingsWindow::DrawSettings(bool* open) {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.06f, 0.06f, 0.06f, 1.00f)); // black
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.40f, 0.70f, 1.00f)); // blue
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
ImVec4(0.25f, 0.50f, 0.85f, 1.00f)); // lighter blue
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive,
ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue
ImGui::PushStyleColor(ImGuiCol_SliderGrab,
ImVec4(0.26f, 0.59f, 0.98f, 0.80f)); // another light blue
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.0f * uiScale);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f * uiScale, 10.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f * uiScale, 10.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.5f * uiScale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f * uiScale, 20.0f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 20.0f * uiScale);
SetupWindow();
DrawCategoryTabs();
DrawMainContent(open);
ImGui::PopStyleVar(8);
ImGui::PopStyleColor(5);
ImGui::End();
}
void SettingsWindow::SetupWindow() {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::Begin("Settings", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::DrawPrettyBackground();
ImGui::SetWindowFontScale(uiScale);
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
SettingsCategory firstCategory =
isGameRunning ? SettingsCategory::General : SettingsCategory::Profiles;
SettingsCategory lastCategory =
currentProfile != "Global" ? SettingsCategory::Experimental : SettingsCategory::Log;
// Navigate categories with Tab / R1 / L1
if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1) || ImGui::IsKeyPressed(ImGuiKey_Tab)) {
int currentIndex = static_cast<int>(currentCategory);
currentCategory == lastCategory
? currentCategory = static_cast<SettingsCategory>(firstCategory)
: currentCategory = static_cast<SettingsCategory>(currentIndex + 1);
}
if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) {
int currentIndex = static_cast<int>(currentCategory);
currentIndex == static_cast<int>(firstCategory)
? currentCategory = lastCategory
: currentCategory = static_cast<SettingsCategory>(currentIndex - 1);
}
}
void SettingsWindow::DrawCategoryTabs() {
ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray
ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor);
float vertSize = (settingsIconSize * uiScale + ImGui::CalcTextSize("Profiles").y) +
ImGui::GetStyle().FramePadding.y * 4.f + 20.0 * uiScale;
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
ImGui::BeginChild("Categories", ImVec2(0, vertSize), true,
child_flags | ImGuiWindowFlags_HorizontalScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30.0f * uiScale, 0.0f));
// Must add categories in enum order for L1/R1 to work correctly
if (!isGameRunning) {
AddCategory("Profiles", profilesTexture, SettingsCategory::Profiles);
}
AddCategory("General", generalTexture, SettingsCategory::General);
AddCategory("Graphics", graphicsTexture, SettingsCategory::Graphics);
AddCategory("Input", inputTexture, SettingsCategory::Input);
AddCategory("Trophy", trophyTexture, SettingsCategory::Trophy);
AddCategory("Game Folders", foldersTexture, SettingsCategory::Folders);
AddCategory("Log", logTexture, SettingsCategory::Log);
if (currentProfile != "Global") {
AddCategory("Experimental", experimentalTexture, SettingsCategory::Experimental);
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::EndChild();
}
void SettingsWindow::AddCategory(std::string name,
std::variant<SDL_Texture*, ImGui::RefCountedTexture> texture,
SettingsCategory category) {
ImGui::SameLine();
ImGui::BeginGroup();
// make button appear hovered as long as category is selected, otherwise dull its hovered color
currentCategory == category
? ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered])
: ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.235f, 0.392f, 0.624f, 1.00f));
ImTextureID id = nullptr;
if (std::holds_alternative<SDL_Texture*>(texture)) {
id = ImTextureID(std::get<SDL_Texture*>(texture));
} else if (std::holds_alternative<ImGui::RefCountedTexture>(texture)) {
id = std::get<ImGui::RefCountedTexture>(texture).GetTexture().im_id;
}
if (id != nullptr) {
if (ImGui::ImageButton(name.c_str(), id,
ImVec2(settingsIconSize * uiScale, settingsIconSize * uiScale))) {
currentCategory = category;
}
}
ImGui::PopStyleColor();
ImGui::SetCursorPosX(
(ImGui::GetCursorPosX() +
(settingsIconSize * uiScale - ImGui::CalcTextSize(name.c_str()).x) * 0.5f) +
ImGui::GetStyle().FramePadding.x);
ImGui::Text("%s", name.c_str());
ImGui::EndGroup();
}
void SettingsWindow::DrawMainContent(bool* open) {
ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray
ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor);
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
std::string centeredText;
currentCategory == SettingsCategory::Folders
? centeredText = "Manage shadPS4 Game Folders"
: centeredText = "Selected Profile: " + currentProfile;
ImGui::Separator();
Overlay::TextCentered(centeredText.c_str());
ImGui::Separator();
if (currentCategory == SettingsCategory::Profiles) {
DrawProfileSelector();
} else if (currentCategory == SettingsCategory::Folders) {
DrawGameFolderManager();
} else {
DrawSettingsTable(currentCategory);
}
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::SetNextItemWidth(300.0f * uiScale);
static float sliderScale2 = 1.0f;
if (ImGui::IsWindowAppearing()) {
sliderScale2 = uiScale;
}
ImGui::SliderFloat("UI Scale", &sliderScale2, 0.25f, 3.0f);
// Only update when user is not interacting with slider
if (ImGui::IsItemDeactivatedAfterEdit()) {
uiScale = sliderScale2;
}
ImGui::SameLine();
// Align buttons right
float buttonsWidth = ImGui::CalcTextSize("Save").x + ImGui::CalcTextSize("Cancel").x +
ImGui::CalcTextSize("Apply").x + ImGui::GetStyle().FramePadding.x * 6.0f +
ImGui::GetStyle().ItemSpacing.x * 2;
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - buttonsWidth);
if (ImGui::Button("Save")) {
closeOnSave = true;
ImGui::OpenPopup("Save Confirmation");
}
ImGui::SameLine();
if (ImGui::Button("Apply")) {
ImGui::OpenPopup("Save Confirmation");
}
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Save Confirmation", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("%s", ("Profile Saved:\n" + currentProfile).c_str());
ImGui::Separator();
if (ImGui::Button("OK", ImVec2(250 * uiScale, 0))) {
std::string profile = currentProfile;
if (currentProfile != "Global") {
profile = currentProfile.substr(0, 9);
}
SaveSettings(profile);
if (closeOnSave) {
DeInit();
*open = false;
closeOnSave = false;
ImGui::CloseCurrentPopup();
} else {
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
DeInit();
*open = false;
}
}
void SettingsWindow::DrawProfileSelector() {
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray
ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor);
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
ImGui::BeginChild("Profile Selection", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true,
child_flags);
ImGui::PopStyleColor();
if (ImGui::BeginTable("ProfilesTable", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("CreateDeleteButton", ImGuiTableColumnFlags_WidthFixed,
400.0f * uiScale);
ImGui::TableSetupColumn("Profile");
for (int i = 0; i < profileIcons.size(); i++) {
const std::filesystem::path customConfigFile =
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
(profileIcons[i].serial + ".json");
const bool gameConfigExists = std::filesystem::exists(customConfigFile);
ImGui::TableNextRow();
ImGui::TableNextColumn();
std::string deleteButtonLabel = "Delete game-specific config##" + std::to_string(i);
if (gameConfigExists) {
// Different shades of red depending on button state
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.0f, 0.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.0f, 0.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.0f, 0.0f, 1.0f));
if (ImGui::Button(deleteButtonLabel.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {
deleteProfileIndex = i;
}
ImGui::PopStyleColor(3);
}
ImGui::TableNextColumn();
std::string profileLabel =
i == 0 ? "Global" : profileIcons[i].serial + " - " + profileIcons[i].title;
if (ImGui::Button(profileLabel.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {
currentProfile = profileLabel;
if (currentProfile == "Global") {
LoadSettings("Global");
} else {
LoadSettings(profileIcons[i].serial);
if (!gameConfigExists) {
SaveSettings(profileIcons[i].serial);
}
}
}
}
ImGui::EndTable();
}
if (deleteProfileIndex != -1) {
ImGui::OpenPopup("Confirm Delete");
}
ImGui::PopStyleVar(3);
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Confirm Delete", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
const std::string title = profileIcons[deleteProfileIndex].title;
const std::string message = "Delete game-specific config file for " + title + "?";
const std::filesystem::path path =
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
(profileIcons[deleteProfileIndex].serial + ".json");
ImGui::Text("%s", message.c_str());
ImGui::Separator();
if (ImGui::Button("OK", ImVec2(120 * uiScale, 0))) {
try {
std::filesystem::remove(path);
} catch (const std::exception& e) {
LOG_ERROR(ImGui, "Could not delete config file {}: {}", path.string(), e.what());
}
deleteProfileIndex = -1;
currentProfile = "Global";
LoadSettings("Global");
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120 * uiScale, 0))) {
deleteProfileIndex = -1;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::EndChild();
}
void SettingsWindow::DrawGameFolderManager() {
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true,
child_flags);
if (ImGui::Button("Add Folder", ImVec2(400.f * uiScale, 0))) {
ImGuiFileDialog::Instance()->OpenDialog("OpenFolder", "Add shadPS4 game folder", nullptr,
".", 1, nullptr,
ImGuiFileDialogFlags_DisableCreateDirectoryButton |
ImGuiFileDialogFlags_DontShowHiddenFiles);
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
}
if (ImGuiFileDialog::Instance()->Display("OpenFolder", child_flags | ImGuiWindowFlags_NoMove)) {
if (ImGuiFileDialog::Instance()->IsOk()) {
GameInstallDir dir;
dir.path = ImGuiFileDialog::Instance()->GetCurrentPath();
dir.enabled = true;
#ifdef WIN32
// replace \ with / on windows
std::string pathString = dir.path.string();
size_t pos = 0;
while ((pos = pathString.find("\\", pos)) != std::string::npos) {
pathString.replace(pos, 1, "/");
pos += 2;
}
dir.path = pathString;
#endif
m_GameInstallDirs.push_back(dir);
SaveInstallDirs();
}
ImGuiFileDialog::Instance()->Close();
}
ImGui::BeginChild("Game Folder List", ImVec2(0, 0), true, child_flags);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 5.0f * uiScale));
if (ImGui::BeginTable("FoldersTable", 3)) {
ImGui::TableSetupColumn("FolderButton", ImGuiTableColumnFlags_WidthFixed, 300.0f * uiScale);
ImGui::TableSetupColumn("FolderEnabled", ImGuiTableColumnFlags_WidthFixed, 0.0f);
ImGui::TableSetupColumn("FolderPath");
for (int i = 0; i < m_GameInstallDirs.size(); i++) {
std::string buttonLabel = "Remove Folder##" + std::to_string(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Button(buttonLabel.c_str(), ImVec2(280 * uiScale, 0))) {
m_GameInstallDirs.erase(m_GameInstallDirs.begin() + i);
SaveInstallDirs();
}
ImGui::TableNextColumn();
std::string checkboxLabel = "##EnableFolder" + std::to_string(i);
bool previousState = m_GameInstallDirs[i].enabled;
ImGui::Checkbox(checkboxLabel.c_str(), &m_GameInstallDirs[i].enabled);
ImGui::SameLine();
ImGui::Dummy(ImVec2(5.0 * uiScale, 0));
if (m_GameInstallDirs[i].enabled != previousState) {
SaveInstallDirs();
}
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", m_GameInstallDirs[i].path.string().c_str());
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::EndChild();
ImGui::EndChild();
}
void SettingsWindow::DrawSettingsTable(SettingsCategory category) {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f * uiScale, 10.0f * uiScale));
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true,
child_flags);
if (category == SettingsCategory::General) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCombo("Console Language", consoleLanguageSetting, languageOptions);
AddSettingSliderInt("Volume", volumeSetting, 0, 500);
AddSettingCheckbox("Show Splash Screen When Launching Game", showSplashSetting);
AddSettingCombo("Audio Backend", audioBackendSetting, audioBackendOptions);
ImGui::EndTable();
}
} else if (category == SettingsCategory::Graphics) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCombo("Display Mode", fullscreenModeSetting, fullscreenModeOptions);
AddSettingCombo("Present Mode", presentModeSetting, presentModeOptions);
AddSettingSliderInt("Window Width", windowWidthSetting, 0, 8000);
AddSettingSliderInt("Window Height", windowHeightSetting, 0, 7000);
AddSettingCheckbox("Enable HDR", hdrAllowedSetting);
AddSettingCheckbox("Enable FSR", fsrEnabledSetting);
if (fsrEnabledSetting) {
AddSettingCheckbox("Enable RCAS", rcasEnabledSetting);
}
if (rcasEnabledSetting && fsrEnabledSetting) {
AddSettingSliderFloat("RCAS Attenuation", rcasAttenuationSetting, 0.0f, 3.0f, 3);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Input) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCheckbox("Enable Motion Controls", motionControlsSetting);
AddSettingCheckbox("Enable Background Controller Input", backgroundControllerSetting);
AddSettingCombo("Hide Cursor", cursorStateSetting, hideCursorOptions);
if (cursorStateSetting == 1) {
AddSettingSliderInt("Hide Cursor Idle Timeout", cursorTimeoutSetting, 1, 10);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Trophy) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCheckbox("Disable Trophy Notification", trophyPopupDisabledSetting);
if (!trophyPopupDisabledSetting) {
AddSettingCombo("Trophy Notification Position", trophySideSetting,
trophySideOptions);
AddSettingSliderFloat("Trophy Notification Duration", trophyDurationSetting, 0.f,
10.f, 1);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Log) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCheckbox("Enable Logging", logEnableSetting);
if (logEnableSetting) {
AddSettingCheckbox("Separate Log Files", logSeparateSetting);
AddSettingCheckbox("Log Sync", logSyncSetting);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Experimental) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingSliderInt("Additional DMem Allocation", extraDmemSetting, 0, 20000);
AddSettingSliderInt("Vblank Frequency", vblankFrequencySetting, 30, 360);
AddSettingCombo("Readbacks Mode", readbacksModeSetting, readbacksModeOptions);
AddSettingCheckbox("Enable Readback Linear Images", readbackLinearImagesSetting);
AddSettingCheckbox("Enable Direct Memory Access", directMemoryAccessSetting);
AddSettingCheckbox("Enable Devkit Console Mode", devkitConsoleSetting);
AddSettingCheckbox("Enable PS4 Neo Mode", neoModeSetting);
AddSettingCheckbox("Enable ShadNet", shadnetEnabledSetting);
AddSettingCheckbox("Set Network Connected to True", connectedNetworkSetting);
AddSettingCheckbox("Enable Shader Cache", pipelineCacheEnabledSetting);
if (pipelineCacheEnabledSetting) {
AddSettingCheckbox("Compress Shader Cache to Zip File",
pipelineCacheArchiveSetting);
}
ImGui::EndTable();
}
}
ImGui::PopStyleVar();
ImGui::EndChild();
}
void SettingsWindow::AddSettingCheckbox(std::string name, bool& value) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::Checkbox(label.c_str(), &value);
}
void SettingsWindow::AddSettingSliderInt(std::string name, int& value, int min, int max) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::SliderInt(label.c_str(), &value, min, max);
}
void SettingsWindow::AddSettingSliderFloat(std::string name, float& value, int min, int max,
int precision) {
std::string label = "##" + name;
std::string precisionString = "%." + std::to_string(precision) + "f";
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::SliderFloat(label.c_str(), &value, min, max, precisionString.c_str());
}
void SettingsWindow::AddSettingCombo(std::string name, int& value,
std::vector<std::string> options) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
const char* combo_value = options[value].c_str();
if (ImGui::BeginCombo(label.c_str(), combo_value)) {
for (int i = 0; i < options.size(); i++) {
const bool selected = (i == value);
if (ImGui::Selectable(options[i].c_str(), selected))
value = i;
// Set the initial focus when opening the combo
if (selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
} // namespace ImGuiEmuSettings

View File

@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <variant>
#include <SDL3/SDL.h>
#include <imgui.h>
#include "big_picture.h"
#include "core/emulator_settings.h"
#include "imgui/imgui_texture.h"
namespace ImGuiEmuSettings {
class SettingsWindow {
public:
SettingsWindow(bool gameRunning);
void DrawSettings(bool* open);
private:
enum class SettingsCategory {
Profiles,
General,
Graphics,
Input,
Trophy,
Folders,
Log,
Experimental,
};
void SaveSettings(std::string profile);
void LoadSettings(std::string profile);
void SaveInstallDirs();
void SetupWindow();
void DeInit();
void GetProfileInfo();
void DrawMainContent(bool* open);
void DrawSettingsTable(SettingsCategory);
void DrawProfileSelector();
void DrawGameFolderManager();
void DrawCategoryTabs();
void AddCategory(std::string name, std::variant<SDL_Texture*, ImGui::RefCountedTexture> texture,
SettingsCategory category);
void AddSettingCheckbox(std::string name, bool& value);
void AddSettingSliderInt(std::string name, int& value, int min, int max);
void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision);
void AddSettingCombo(std::string name, int& value, std::vector<std::string> options);
int GetComboIndex(std::string selection, std::vector<std::string> options);
std::vector<BigPictureMode::IconInfo> profileIcons = {};
std::vector<GameInstallDir> m_GameInstallDirs = {};
float uiScale = 1.0f;
SettingsCategory currentCategory = SettingsCategory::Profiles;
std::string currentProfile = "Global";
std::string runningGameSerial = "";
bool isGameRunning = false;
bool closeOnSave = false;
int deleteProfileIndex = -1;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> profilesTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> generalTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> experimentalTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> graphicsTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> inputTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> trophyTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> logTexture;
std::variant<SDL_Texture*, ImGui::RefCountedTexture> foldersTexture;
//////////////////// options for comboboxes
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}};
std::vector<std::string> languageOptions; // assigned from keys above
const std::vector<std::string> fullscreenModeOptions = {"Windowed", "Fullscreen",
"Fullscreen (Borderless)"};
const std::vector<std::string> audioBackendOptions = {"SDL", "OpenAL"};
const std::vector<std::string> presentModeOptions = {"Mailbox", "Fifo", "Immediate"};
const std::vector<std::string> hideCursorOptions = {"Never", "Idle", "Always"};
const std::vector<std::string> trophySideOptions = {"left", "right", "top", "bottom"};
const std::vector<std::string> readbacksModeOptions = {"Disabled", "Relaxed", "Precise"};
//////////////// Setting Variables
//////////////// Note:: Use int for all comboboxes as needed by ImGui
// General tab
int consoleLanguageSetting;
int volumeSetting;
bool showSplashSetting;
int audioBackendSetting;
// Graphics tab
int fullscreenModeSetting;
int presentModeSetting;
int windowWidthSetting;
int windowHeightSetting;
bool hdrAllowedSetting;
bool fsrEnabledSetting;
bool rcasEnabledSetting;
float rcasAttenuationSetting;
// Input tab
bool motionControlsSetting;
bool backgroundControllerSetting;
int cursorStateSetting;
int cursorTimeoutSetting;
// Trophy tab
bool trophyPopupDisabledSetting;
int trophySideSetting;
float trophyDurationSetting;
// Log tab
bool logEnableSetting;
bool logSeparateSetting;
bool logSyncSetting;
// Experimental tab
int readbacksModeSetting;
bool readbackLinearImagesSetting;
bool directMemoryAccessSetting;
bool devkitConsoleSetting;
bool neoModeSetting;
bool shadnetEnabledSetting;
bool connectedNetworkSetting;
bool pipelineCacheEnabledSetting;
bool pipelineCacheArchiveSetting;
int extraDmemSetting;
int vblankFrequencySetting;
};
} // namespace ImGuiEmuSettings

View File

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include "imgui/imgui_std.h"
#include "settings_dialog_layer.h"
namespace ImGuiEmuSettings {
static bool running = false;
static std::optional<SettingsLayer> settingsLayer = std::nullopt;
SettingsLayer::SettingsLayer() {
AddLayer(this);
}
SettingsLayer::~SettingsLayer() {}
void SettingsLayer::Finish() {
RemoveLayer(this);
settingsLayer.reset();
}
void SettingsLayer::Draw() {
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_SETTINGS_WINDOW]);
settingsWindow.DrawSettings(&running);
ImGui::PopFont();
if (!running) {
Finish();
}
}
void OpenInGameSettingsDialog() {
if (settingsLayer.has_value()) {
return;
}
settingsLayer.emplace();
running = true;
}
} // namespace ImGuiEmuSettings

View File

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <SDL3/SDL.h>
#include "imgui/imgui_layer.h"
#include "settings_dialog_imgui.h"
namespace ImGuiEmuSettings {
class SettingsLayer final : public ImGui::Layer {
public:
SettingsLayer();
~SettingsLayer() override;
void Draw() override;
private:
void Finish();
SettingsWindow settingsWindow = SettingsWindow(true);
};
void OpenInGameSettingsDialog();
}; // namespace ImGuiEmuSettings

View File

@ -15,6 +15,7 @@
#define IMGUI_FONT_TEXT 0
#define IMGUI_FONT_MONO 1
#define IMGUI_FONT_TEXT_BIG 2
#define IMGUI_FONT_SETTINGS_WINDOW 3
namespace ImGui {

View File

@ -25,6 +25,7 @@ constexpr int kNotoSansCjkFontIndexTc = 3;
constexpr ImWchar kLatinExtendedRanges[] = {
0x0100, 0x024F, // Latin Extended-A + Latin Extended-B
0x1E00, 0x1EFF, // Latin Extended Additional
0x2122, 0x2122, // TM symbol
0,
};

View File

@ -75,6 +75,11 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
// Avoid exploding atlas size on Metal/MoltenVK when CJK fallback is enabled.
FontStack::AddPrimaryUiFont(io.Fonts, 128.0f, console_language, font_cfg, false);
// Big Picture
FontStack::AddPrimaryUiFont(ImGui::GetIO().Fonts, 64.0f, EmulatorSettings.GetConsoleLanguage(),
font_cfg, true);
io.Fonts->Build();
io.FontGlobalScale = 0.5f;

View File

@ -1,802 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <ranges>
#include <ImGuiFileDialog.h>
#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"
CMRC_DECLARE(res);
namespace BigPictureMode {
//////////////////// options for comboboxes
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}};
std::vector<std::string> languageOptions; // assigned from keys above
const std::vector<std::string> fullscreenModeOptions = {"Windowed", "Fullscreen",
"Fullscreen (Borderless)"};
const std::vector<std::string> audioBackendOptions = {"SDL", "OpenAL"};
const std::vector<std::string> presentModeOptions = {"Mailbox", "Fifo", "Immediate"};
const std::vector<std::string> hideCursorOptions = {"Never", "Idle", "Always"};
const std::vector<std::string> trophySideOptions = {"left", "right", "top", "bottom"};
const std::vector<std::string> readbacksModeOptions = {"Disabled", "Relaxed", "Precise"};
//////////////// Setting Variables
//////////////// Note:: Use int for all comboboxes as needed by ImGui
// General tab
int consoleLanguageSetting;
int volumeSetting;
bool showSplashSetting;
int audioBackendSetting;
// Graphics tab
int fullscreenModeSetting;
int presentModeSetting;
int windowWidthSetting;
int windowHeightSetting;
bool hdrAllowedSetting;
bool fsrEnabledSetting;
bool rcasEnabledSetting;
float rcasAttenuationSetting;
// Input tab
bool motionControlsSetting;
bool backgroundControllerSetting;
int cursorStateSetting;
int cursorTimeoutSetting;
// Trophy tab
bool trophyPopupDisabledSetting;
int trophySideSetting;
float trophyDurationSetting;
// Log tab
bool logEnableSetting;
bool logSeparateSetting;
bool logSyncSetting;
// Experimental tab
int readbacksModeSetting;
bool readbackLinearImagesSetting;
bool directMemoryAccessSetting;
bool devkitConsoleSetting;
bool neoModeSetting;
bool shadnetEnabledSetting;
bool connectedNetworkSetting;
bool pipelineCacheEnabledSetting;
bool pipelineCacheArchiveSetting;
int extraDmemSetting;
int vblankFrequencySetting;
//////////////// Texture data
SDL_Texture* profilesTexture;
SDL_Texture* generalTexture;
SDL_Texture* globalSettingsTexture;
SDL_Texture* experimentalTexture;
SDL_Texture* graphicsTexture;
SDL_Texture* inputTexture;
SDL_Texture* trophyTexture;
SDL_Texture* logTexture;
SDL_Texture* foldersTexture;
//////////////// Gui variable
const float gameImageSize = 200.f;
const float settingsIconSize = 125.f;
std::vector<Game> settingsProfileVec = {};
std::vector<GameInstallDir> m_GameInstallDirs = {};
float uiScale = 1.0f;
SDL_Renderer* renderer;
SettingsCategory currentCategory = SettingsCategory::Profiles;
std::string currentProfile = "Global";
bool closeOnSave = false;
bool showFileDialog = false;
void Init() {
auto languageKeys = std::views::keys(languageMap);
languageOptions.assign(languageKeys.begin(), languageKeys.end());
currentProfile = "Global";
currentCategory = SettingsCategory::Profiles;
LoadSettings("Global");
m_GameInstallDirs = EmulatorSettings.GetAllGameInstallDirs();
SDL_Window* window = SDL_GetKeyboardFocus();
renderer = SDL_GetRenderer(window);
LoadEmbeddedTexture("src/images/big_picture/settings.png", generalTexture);
LoadEmbeddedTexture("src/images/big_picture/profiles.png", profilesTexture);
LoadEmbeddedTexture("src/images/big_picture/global-settings.png", globalSettingsTexture);
LoadEmbeddedTexture("src/images/big_picture/experimental.png", experimentalTexture);
LoadEmbeddedTexture("src/images/big_picture/graphics.png", graphicsTexture);
LoadEmbeddedTexture("src/images/big_picture/controller.png", inputTexture);
LoadEmbeddedTexture("src/images/big_picture/trophy.png", trophyTexture);
LoadEmbeddedTexture("src/images/big_picture/log.png", logTexture);
LoadEmbeddedTexture("src/images/big_picture/folder.png", foldersTexture);
GetGameInfo(settingsProfileVec, true, globalSettingsTexture);
uiScale = static_cast<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 | ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::IsWindowAppearing()) {
Init();
closeOnSave = false;
}
ImGui::DrawPrettyBackground();
ImGui::SetWindowFontScale(uiScale);
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
ImVec4 settingsColor = ImVec4(0.1f, 0.1f, 0.12f, 0.8f); // Darker gray
ImGui::PushStyleColor(ImGuiCol_ChildBg, settingsColor);
float vertSize = (settingsIconSize * uiScale + ImGui::CalcTextSize("Profiles").y) +
ImGui::GetStyle().FramePadding.x * 2.f * uiScale + 20.0 * uiScale;
ImGui::BeginChild("Categories", ImVec2(0, vertSize), true,
child_flags | ImGuiWindowFlags_HorizontalScrollbar);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30.0f * uiScale, 0.0f));
// Must add categories in enum order for L1/R1 to work correctly, with experimental last
AddCategory("Profiles", profilesTexture, SettingsCategory::Profiles);
AddCategory("General", generalTexture, SettingsCategory::General);
AddCategory("Graphics", graphicsTexture, SettingsCategory::Graphics);
AddCategory("Input", inputTexture, SettingsCategory::Input);
AddCategory("Trophy", trophyTexture, SettingsCategory::Trophy);
AddCategory("Game Folders", foldersTexture, SettingsCategory::Folders);
AddCategory("Log", logTexture, SettingsCategory::Log);
if (currentProfile != "Global")
AddCategory("Experimental", experimentalTexture, SettingsCategory::Experimental);
ImGui::PopStyleVar();
ImGui::EndChild(); // Categories
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
ImGui::BeginChild("ContentRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true,
child_flags);
ImGui::PopStyleColor();
LoadCategory(currentCategory);
ImGui::EndChild();
ImGui::Separator();
ImGui::SetNextItemWidth(300.0f * uiScale);
static float sliderScale2 = 1.0f;
if (ImGui::IsWindowAppearing()) {
sliderScale2 = uiScale;
}
ImGui::SliderFloat("UI Scale", &sliderScale2, 0.25f, 3.0f);
// Only update when user is not interacting with slider
if (ImGui::IsItemDeactivatedAfterEdit()) {
uiScale = sliderScale2;
}
ImGui::SameLine();
// Align buttons right
float buttonsWidth = ImGui::CalcTextSize("Save").x + ImGui::CalcTextSize("Cancel").x +
ImGui::CalcTextSize("Apply").x +
ImGui::GetStyle().FramePadding.x * 6.0f +
ImGui::GetStyle().ItemSpacing.x * 2;
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - buttonsWidth);
if (ImGui::Button("Save")) {
closeOnSave = true;
ImGui::OpenPopup("Save Confirmation");
}
ImGui::SameLine();
if (ImGui::Button("Apply")) {
ImGui::OpenPopup("Save Confirmation");
}
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Save Confirmation", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("%s", ("Profile Saved:\n" + currentProfile).c_str());
ImGui::Separator();
if (ImGui::Button("OK", ImVec2(250 * uiScale, 0))) {
std::string profile = currentProfile;
if (currentProfile != "Global") {
profile = currentProfile.substr(0, 9);
}
SaveSettings(profile);
if (closeOnSave) {
DeInit();
*open = false;
ImGui::CloseCurrentPopup();
} else {
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
DeInit();
*open = false;
}
SettingsCategory lastCategory =
currentProfile != "Global" ? SettingsCategory::Experimental : SettingsCategory::Log;
// Navigate categories with Tab / R1 / L1
if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1) || ImGui::IsKeyPressed(ImGuiKey_Tab)) {
int currentIndex = static_cast<int>(currentCategory);
currentCategory == lastCategory
? currentCategory = static_cast<SettingsCategory>(0)
: currentCategory = static_cast<SettingsCategory>(currentIndex + 1);
}
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) {
if (category != SettingsCategory::Folders) {
ImGui::TextColored(ImVec4(0.00f, 1.00f, 1.00f, 1.00f), "%s",
("Selected Profile: " + currentProfile).c_str()); // Dark Blue
}
ImGui::Dummy(ImVec2(0, 20.f * uiScale));
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f * uiScale, 10.0f * uiScale));
ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NavFlattened;
if (category == SettingsCategory::General) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCombo("Console Language", consoleLanguageSetting, languageOptions);
AddSettingSliderInt("Volume", volumeSetting, 0, 500);
AddSettingBool("Show Splash Screen When Launching Game", showSplashSetting);
AddSettingCombo("Audio Backend", audioBackendSetting, audioBackendOptions);
ImGui::EndTable();
}
} else if (category == SettingsCategory::Graphics) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingCombo("Display Mode", fullscreenModeSetting, fullscreenModeOptions);
AddSettingCombo("Present Mode", presentModeSetting, presentModeOptions);
AddSettingSliderInt("Window Width", windowWidthSetting, 0, 8000);
AddSettingSliderInt("Window Height", windowHeightSetting, 0, 7000);
AddSettingBool("Enable HDR", hdrAllowedSetting);
AddSettingBool("Enable FSR", fsrEnabledSetting);
if (fsrEnabledSetting) {
AddSettingBool("Enable RCAS", rcasEnabledSetting);
}
if (rcasEnabledSetting && fsrEnabledSetting) {
AddSettingSliderFloat("RCAS Attenuation", rcasAttenuationSetting, 0.0f, 3.0f, 3);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Input) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingBool("Enable Motion Controls", motionControlsSetting);
AddSettingBool("Enable Background Controller Input", backgroundControllerSetting);
AddSettingCombo("Hide Cursor", cursorStateSetting, hideCursorOptions);
if (cursorStateSetting == 1) {
AddSettingSliderInt("Hide Cursor Idle Timeout", cursorTimeoutSetting, 1, 10);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Trophy) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingBool("Disable Trophy Notification", trophyPopupDisabledSetting);
if (!trophyPopupDisabledSetting) {
AddSettingCombo("Trophy Notification Position", trophySideSetting,
trophySideOptions);
AddSettingSliderFloat("Trophy Notification Duration", trophyDurationSetting, 0.f,
10.f, 1);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Folders) {
if (ImGui::Button("Add Folder", ImVec2(400.f * uiScale, 0))) {
ImGuiFileDialog::Instance()->OpenDialog(
"OpenFolder", "Add shadPS4 game folder", nullptr, ".", 1, nullptr,
ImGuiFileDialogFlags_DisableCreateDirectoryButton |
ImGuiFileDialogFlags_DontShowHiddenFiles);
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
}
if (ImGuiFileDialog::Instance()->Display("OpenFolder",
child_flags | ImGuiWindowFlags_NoMove)) {
if (ImGuiFileDialog::Instance()->IsOk()) {
GameInstallDir dir;
dir.path = ImGuiFileDialog::Instance()->GetCurrentPath();
dir.enabled = true;
#ifdef WIN32
// replace \ with / on windows
std::string pathString = dir.path.string();
size_t pos = 0;
while ((pos = pathString.find("\\", pos)) != std::string::npos) {
pathString.replace(pos, 1, "/");
pos += 2;
}
dir.path = pathString;
#endif
m_GameInstallDirs.push_back(dir);
SaveInstallDirs();
}
ImGuiFileDialog::Instance()->Close();
}
} else if (category == SettingsCategory::Log) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingBool("Enable Logging", logEnableSetting);
if (logEnableSetting) {
AddSettingBool("Separate Log Files", logSeparateSetting);
AddSettingBool("Log Sync", logSyncSetting);
}
ImGui::EndTable();
}
} else if (category == SettingsCategory::Experimental) {
if (ImGui::BeginTable("SettingsTable", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 500.0f * uiScale);
ImGui::TableSetupColumn("Value");
AddSettingSliderInt("Additional DMem Allocation", extraDmemSetting, 0, 20000);
AddSettingSliderInt("Vblank Frequency", vblankFrequencySetting, 30, 360);
AddSettingCombo("Readbacks Mode", readbacksModeSetting, readbacksModeOptions);
AddSettingBool("Enable Readback Linear Images", readbackLinearImagesSetting);
AddSettingBool("Enable Direct Memory Access", directMemoryAccessSetting);
AddSettingBool("Enable Devkit Console Mode", devkitConsoleSetting);
AddSettingBool("Enable PS4 Neo Mode", neoModeSetting);
AddSettingBool("Enable ShadNet", shadnetEnabledSetting);
AddSettingBool("Set Network Connected to True", connectedNetworkSetting);
AddSettingBool("Enable Shader Cache", pipelineCacheEnabledSetting);
if (pipelineCacheEnabledSetting) {
AddSettingBool("Compress Shader Cache to Zip File", pipelineCacheArchiveSetting);
}
ImGui::EndTable();
}
}
ImGui::PopStyleVar();
// Child Window if Needed
if (category == SettingsCategory::Profiles) {
ImGui::BeginChild("ProfileSelect", ImVec2(0, 0), true, child_flags);
Overlay::TextCentered("Select Global or Game-Specific Settings Profile");
SetProfileIcons(settingsProfileVec);
ImGui::EndChild();
} else if (category == SettingsCategory::Folders) {
ImGui::BeginChild("Game Folder List", ImVec2(0, 0), true, child_flags);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 5.0f * uiScale));
if (ImGui::BeginTable("FoldersTable", 3)) {
ImGui::TableSetupColumn("FolderButton", ImGuiTableColumnFlags_WidthFixed,
300.0f * uiScale);
ImGui::TableSetupColumn("FolderEnabled", ImGuiTableColumnFlags_WidthFixed, 0.0f);
ImGui::TableSetupColumn("FolderPath");
for (int i = 0; i < m_GameInstallDirs.size(); i++) {
std::string buttonLabel = "Remove Folder##" + std::to_string(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Button(buttonLabel.c_str(), ImVec2(280 * uiScale, 0))) {
m_GameInstallDirs.erase(m_GameInstallDirs.begin() + i);
SaveInstallDirs();
}
ImGui::TableNextColumn();
std::string checkboxLabel = "##EnableFolder" + std::to_string(i);
bool previousState = m_GameInstallDirs[i].enabled;
ImGui::Checkbox(checkboxLabel.c_str(), &m_GameInstallDirs[i].enabled);
ImGui::SameLine();
ImGui::Dummy(ImVec2(5.0 * uiScale, 0));
if (m_GameInstallDirs[i].enabled != previousState) {
SaveInstallDirs();
}
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", m_GameInstallDirs[i].path.string().c_str());
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::EndChild();
}
}
void SaveSettings(std::string profile) {
const bool isSpecific = currentProfile != "Global";
/////////// General Tab
EmulatorSettings.SetConsoleLanguage(languageMap.at(languageOptions.at(consoleLanguageSetting)),
isSpecific);
EmulatorSettings.SetVolumeSlider(volumeSetting, isSpecific);
EmulatorSettings.SetShowSplash(showSplashSetting, isSpecific);
EmulatorSettings.SetAudioBackend(audioBackendSetting, isSpecific);
/////////// Graphics Tab
bool isFullscreen = fullscreenModeSetting != 0;
EmulatorSettings.SetFullScreen(isFullscreen);
EmulatorSettings.SetFullScreenMode(fullscreenModeOptions.at(fullscreenModeSetting), isSpecific);
EmulatorSettings.SetPresentMode(presentModeOptions.at(presentModeSetting), isSpecific);
EmulatorSettings.SetWindowHeight(windowHeightSetting, isSpecific);
EmulatorSettings.SetWindowWidth(windowWidthSetting, isSpecific);
EmulatorSettings.SetHdrAllowed(hdrAllowedSetting, isSpecific);
EmulatorSettings.SetFsrEnabled(fsrEnabledSetting, isSpecific);
EmulatorSettings.SetRcasEnabled(rcasEnabledSetting, isSpecific);
EmulatorSettings.SetRcasAttenuation(static_cast<int>(rcasAttenuationSetting * 1000),
isSpecific);
/////////// Input Tab
EmulatorSettings.SetMotionControlsEnabled(motionControlsSetting, isSpecific);
EmulatorSettings.SetBackgroundControllerInput(backgroundControllerSetting, isSpecific);
EmulatorSettings.SetCursorState(cursorStateSetting, isSpecific);
EmulatorSettings.SetCursorHideTimeout(cursorTimeoutSetting, isSpecific);
/////////// Trophy Tab
EmulatorSettings.SetTrophyPopupDisabled(trophyPopupDisabledSetting, isSpecific);
EmulatorSettings.SetTrophyNotificationSide(trophySideOptions.at(trophySideSetting), isSpecific);
EmulatorSettings.SetTrophyNotificationDuration(static_cast<double>(trophyDurationSetting));
/////////// Log Tab
EmulatorSettings.SetLogEnable(logEnableSetting, isSpecific);
EmulatorSettings.SetLogSeparate(logSeparateSetting, isSpecific);
EmulatorSettings.SetLogSync(logSyncSetting, isSpecific);
/////////// Experimental Tab
if (isSpecific) {
EmulatorSettings.SetReadbacksMode(readbacksModeSetting, true);
EmulatorSettings.SetReadbackLinearImagesEnabled(readbackLinearImagesSetting, true);
EmulatorSettings.SetDirectMemoryAccessEnabled(directMemoryAccessSetting, true);
EmulatorSettings.SetDevKit(devkitConsoleSetting, true);
EmulatorSettings.SetNeo(neoModeSetting, true);
EmulatorSettings.SetShadNetEnabled(shadnetEnabledSetting, true);
EmulatorSettings.SetConnectedToNetwork(connectedNetworkSetting, true);
EmulatorSettings.SetPipelineCacheEnabled(pipelineCacheEnabledSetting, true);
EmulatorSettings.SetPipelineCacheArchived(pipelineCacheArchiveSetting, true);
EmulatorSettings.SetExtraDmemInMBytes(extraDmemSetting, true);
EmulatorSettings.SetVblankFrequency(vblankFrequencySetting, true);
}
if (!isSpecific) {
EmulatorSettings.Save();
} else {
EmulatorSettings.Save(profile);
}
}
void LoadSettings(std::string profile) {
const bool isSpecific = currentProfile != "Global";
if (!isSpecific) {
EmulatorSettings.Load();
} else {
EmulatorSettings.Load(profile);
}
/////////// General Tab
int languageIndex = EmulatorSettings.GetConsoleLanguage();
std::string language;
for (const auto& [key, value] : languageMap) {
if (value == languageIndex) {
language = key;
}
}
consoleLanguageSetting = GetComboIndex(language, languageOptions);
volumeSetting = EmulatorSettings.GetVolumeSlider();
showSplashSetting = EmulatorSettings.IsShowSplash();
audioBackendSetting = EmulatorSettings.GetAudioBackend();
/////////// Graphics Tab
fullscreenModeSetting =
GetComboIndex(EmulatorSettings.GetFullScreenMode(), fullscreenModeOptions);
presentModeSetting = GetComboIndex(EmulatorSettings.GetPresentMode(), presentModeOptions);
windowHeightSetting = EmulatorSettings.GetWindowHeight();
windowWidthSetting = EmulatorSettings.GetWindowWidth();
hdrAllowedSetting = EmulatorSettings.IsHdrAllowed();
fsrEnabledSetting = EmulatorSettings.IsFsrEnabled();
rcasEnabledSetting = EmulatorSettings.IsRcasEnabled();
rcasAttenuationSetting = static_cast<float>(EmulatorSettings.GetRcasAttenuation() * 0.001f);
/////////// Input Tab
motionControlsSetting = EmulatorSettings.IsMotionControlsEnabled();
backgroundControllerSetting = EmulatorSettings.IsBackgroundControllerInput();
cursorStateSetting = EmulatorSettings.GetCursorState();
cursorTimeoutSetting = EmulatorSettings.GetCursorHideTimeout();
/////////// Trophy Tab
trophyPopupDisabledSetting = EmulatorSettings.IsTrophyPopupDisabled();
trophySideSetting =
GetComboIndex(EmulatorSettings.GetTrophyNotificationSide(), trophySideOptions);
trophyDurationSetting = static_cast<float>(EmulatorSettings.GetTrophyNotificationDuration());
/////////// Log Tab
logEnableSetting = EmulatorSettings.IsLogEnable();
logSeparateSetting = EmulatorSettings.IsLogSeparate();
logSyncSetting = EmulatorSettings.IsLogSync();
/////////// Experimental Tab
if (isSpecific) {
readbacksModeSetting = EmulatorSettings.GetReadbacksMode();
readbackLinearImagesSetting = EmulatorSettings.IsReadbackLinearImagesEnabled();
directMemoryAccessSetting = EmulatorSettings.IsDirectMemoryAccessEnabled();
devkitConsoleSetting = EmulatorSettings.IsDevKit();
neoModeSetting = EmulatorSettings.IsNeo();
shadnetEnabledSetting = EmulatorSettings.IsShadNetEnabled();
connectedNetworkSetting = EmulatorSettings.IsConnectedToNetwork();
pipelineCacheEnabledSetting = EmulatorSettings.IsPipelineCacheEnabled();
pipelineCacheArchiveSetting = EmulatorSettings.IsPipelineCacheArchived();
extraDmemSetting = EmulatorSettings.GetExtraDmemInMBytes();
vblankFrequencySetting = EmulatorSettings.GetVblankFrequency();
}
}
void AddCategory(std::string name, SDL_Texture* texture, SettingsCategory category) {
ImGui::SameLine();
ImGui::BeginGroup();
// make button appear hovered as long as category is selected, otherwise dull it's hovered color
currentCategory == category
? ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered])
: ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.235f, 0.392f, 0.624f, 1.00f));
if (ImGui::ImageButton(name.c_str(), ImTextureID(texture),
ImVec2(settingsIconSize * uiScale, settingsIconSize * uiScale))) {
currentCategory = category;
}
ImGui::PopStyleColor();
ImGui::SetCursorPosX(
(ImGui::GetCursorPosX() +
(settingsIconSize * uiScale - ImGui::CalcTextSize(name.c_str()).x) * 0.5f) +
ImGui::GetStyle().FramePadding.x);
ImGui::Text("%s", name.c_str());
ImGui::EndGroup();
}
void AddSettingBool(std::string name, bool& value) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::Checkbox(label.c_str(), &value);
}
void AddSettingSliderInt(std::string name, int& value, int min, int max) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::SliderInt(label.c_str(), &value, min, max);
}
void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision) {
std::string label = "##" + name;
std::string precisionString = "%." + std::to_string(precision) + "f";
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::SliderFloat(label.c_str(), &value, min, max, precisionString.c_str());
}
void AddSettingCombo(std::string name, int& value, std::vector<std::string> options) {
std::string label = "##" + name;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", name.c_str());
ImGui::TableNextColumn();
const char* combo_value = options[value].c_str();
if (ImGui::BeginCombo(label.c_str(), combo_value)) {
for (int i = 0; i < options.size(); i++) {
const bool selected = (i == value);
if (ImGui::Selectable(options[i].c_str(), selected))
value = i;
// Set the initial focus when opening the combo
if (selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
int GetComboIndex(std::string selection, std::vector<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;
}
}
}
void SaveInstallDirs() {
std::string profile;
const bool isGlobal = currentProfile == "Global";
if (!isGlobal) {
profile = currentProfile.substr(0, 9);
}
if (!isGlobal) {
EmulatorSettings.Load();
}
EmulatorSettings.SetAllGameInstallDirs(m_GameInstallDirs);
EmulatorSettings.Save();
if (!isGlobal) {
EmulatorSettings.Load(profile);
}
GetGameInfo(settingsProfileVec, true, globalSettingsTexture);
}
} // namespace BigPictureMode

View File

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#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,
Folders,
Log,
Experimental,
};
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 SaveInstallDirs();
void AddSettingBool(std::string name, bool& value);
void AddSettingSliderInt(std::string name, int& value, int min, int max);
void AddSettingSliderFloat(std::string name, float& value, int min, int max, int precision);
void AddSettingCombo(std::string name, int& value, std::vector<std::string> options);
int GetComboIndex(std::string selection, std::vector<std::string> options);
} // namespace BigPictureMode

View File

@ -25,6 +25,7 @@
#include "core/devtools/layer.h"
#include "core/emulator_settings.h"
#include "core/emulator_state.h"
#include "imgui/big_picture/settings_dialog_layer.h"
#include "input/controller.h"
#include "input/input_mouse.h"
@ -180,6 +181,7 @@ std::filesystem::path GetInputConfigFile(const std::string& game_id) {
{"hotkey_quit", "lctrl, lshift, end"},
{"hotkey_volume_up", "kpplus"},
{"hotkey_volume_down", "kpminus"},
{"hotkey_emulator_settings", "f3"},
};
std::string legacy_capture_binding;
bool legacy_capture_binding_found = false;
@ -799,6 +801,9 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
case HOTKEY_QUIT:
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
break;
case HOTKEY_OPEN_EMULATOR_SETTINGS:
ImGuiEmuSettings::OpenInGameSettingsDialog();
break;
case KEY_TOGGLE:
// noop
break;

View File

@ -64,6 +64,7 @@
#define HOTKEY_ADD_VIRTUAL_USER 0xf000000c
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d
#define HOTKEY_SCREENSHOT_WITH_OVERLAYS 0xf000000e
#define HOTKEY_OPEN_EMULATOR_SETTINGS 0xf000000f
#define SDL_UNMAPPED UINT32_MAX - 1
@ -165,6 +166,7 @@ const std::map<std::string, u32> string_to_hotkey_map = {
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
{"hotkey_emulator_settings", HOTKEY_OPEN_EMULATOR_SETTINGS},
};
const std::map<std::string, AxisMapping> string_to_axis_map = {
@ -530,7 +532,7 @@ public:
class ControllerAllOutputs {
public:
static constexpr u64 output_count = 41;
static constexpr u64 output_count = 42;
std::array<ControllerOutput, output_count> data = {
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput(LEFTJOYSTICK_HALFMODE),
@ -583,6 +585,7 @@ public:
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
ControllerOutput(HOTKEY_VOLUME_UP),
ControllerOutput(HOTKEY_VOLUME_DOWN),
ControllerOutput(HOTKEY_OPEN_EMULATOR_SETTINGS),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
};

View File

@ -20,7 +20,7 @@
#include "core/file_sys/fs.h"
#include "core/ipc/ipc.h"
#include "emulator.h"
#include "imgui/big_picture.h"
#include "imgui/big_picture/big_picture.h"
#ifdef _WIN32
#include <windows.h>