diff --git a/CMakeLists.txt b/CMakeLists.txt index 659c0d84f..4318b7ed1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,6 +280,7 @@ set(SOURCE_FILES src/emulator.cpp src/io_file.cpp src/config.cpp src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp src/http_server.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/miniaudio.cpp src/renderdoc.cpp + src/frontend_settings.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -361,7 +362,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/PICA/pica_vert_config.hpp include/sdl_sensors.hpp include/PICA/draw_acceleration.hpp include/renderdoc.hpp include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp - include/services/dsp_firmware_db.hpp + include/services/dsp_firmware_db.hpp include/frontend_settings.hpp ) cmrc_add_resource_library( diff --git a/include/config.hpp b/include/config.hpp index 5a7c7fff9..7361a40d1 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -3,6 +3,7 @@ #include "audio/dsp_core.hpp" #include "renderer.hpp" +#include "frontend_settings.hpp" struct AudioDeviceConfig { float volumeRaw = 1.0f; @@ -86,8 +87,9 @@ struct EmulatorConfig { WindowSettings windowSettings; AudioDeviceConfig audioDeviceConfig; + FrontendSettings frontendSettings; EmulatorConfig(const std::filesystem::path& path); void load(); void save(); -}; +}; \ No newline at end of file diff --git a/include/frontend_settings.hpp b/include/frontend_settings.hpp new file mode 100644 index 000000000..6cfa066c3 --- /dev/null +++ b/include/frontend_settings.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +// Some UI settings that aren't fully frontend-dependent. Note: Not all frontends will support the same settings. +// Note: Any enums should ideally be ordered in the same order we want to show them in UI dropdown menus, so that we can cast indices to enums +// directly. +struct FrontendSettings { + enum class Theme : int { + System = 0, + Light = 1, + Dark = 2, + GreetingsCat = 3, + Cream = 4, + }; + + // Different panda-themed window icons + enum class WindowIcon : int { + Rpog = 0, + Rsyn = 1, + }; + + Theme theme = Theme::Dark; + WindowIcon icon = WindowIcon::Rpog; + + static Theme themeFromString(std::string inString); + static const char* themeToString(Theme theme); + + static WindowIcon iconFromString(std::string inString); + static const char* iconToString(WindowIcon icon); +}; diff --git a/include/panda_qt/config_window.hpp b/include/panda_qt/config_window.hpp index eb1caedcb..3cf4a1c82 100644 --- a/include/panda_qt/config_window.hpp +++ b/include/panda_qt/config_window.hpp @@ -15,6 +15,7 @@ #include #include "emulator.hpp" +#include "frontend_settings.hpp" class ConfigWindow : public QDialog { Q_OBJECT @@ -23,21 +24,8 @@ class ConfigWindow : public QDialog { using ConfigCallback = std::function; using IconCallback = std::function; - enum class Theme : int { - System = 0, - Light = 1, - Dark = 2, - GreetingsCat = 3, - Cream = 4, - }; - - enum class WindowIcon : int { - Rpog = 0, - Rsyn = 1, - }; - - Theme currentTheme; - WindowIcon currentIcon; + using Theme = FrontendSettings::Theme; + using WindowIcon = FrontendSettings::WindowIcon; QTextEdit* helpText = nullptr; QListWidget* widgetList = nullptr; @@ -54,8 +42,8 @@ class ConfigWindow : public QDialog { IconCallback updateIcon; void addWidget(QWidget* widget, QString title, QString icon, QString helpText); - void setTheme(Theme theme); - void setIcon(WindowIcon icon); + void setTheme(FrontendSettings::Theme theme); + void setIcon(FrontendSettings::WindowIcon icon); public: ConfigWindow(ConfigCallback configCallback, IconCallback iconCallback, const EmulatorConfig& config, QWidget* parent = nullptr); diff --git a/src/config.cpp b/src/config.cpp index 93aed1060..a8c88a68f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -130,6 +130,16 @@ void EmulatorConfig::load() { sdWriteProtected = toml::find_or(sd, "WriteProtectVirtualSD", false); } } + + if (data.contains("UI")) { + auto uiResult = toml::expect(data.at("UI")); + if (uiResult.is_ok()) { + auto ui = uiResult.unwrap(); + + frontendSettings.theme = FrontendSettings::themeFromString(toml::find_or(ui, "Theme", "dark")); + frontendSettings.icon = FrontendSettings::iconFromString(toml::find_or(ui, "WindowIcon", "rpog")); + } + } } void EmulatorConfig::save() { @@ -186,6 +196,9 @@ void EmulatorConfig::save() { data["SD"]["UseVirtualSD"] = sdCardInserted; data["SD"]["WriteProtectVirtualSD"] = sdWriteProtected; + data["UI"]["Theme"] = std::string(FrontendSettings::themeToString(frontendSettings.theme)); + data["UI"]["WindowIcon"] = std::string(FrontendSettings::iconToString(frontendSettings.icon)); + std::ofstream file(path, std::ios::out); file << data; file.close(); diff --git a/src/frontend_settings.cpp b/src/frontend_settings.cpp new file mode 100644 index 000000000..5a489fc7b --- /dev/null +++ b/src/frontend_settings.cpp @@ -0,0 +1,59 @@ +#include "frontend_settings.hpp" + +#include +#include +#include + +// Frontend setting serialization/deserialization functions + +FrontendSettings::Theme FrontendSettings::themeFromString(std::string inString) { + // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + static const std::unordered_map map = { + {"system", Theme::System}, {"light", Theme::Light}, {"dark", Theme::Dark}, {"greetingscat", Theme::GreetingsCat}, {"cream", Theme::Cream}, + }; + + if (auto search = map.find(inString); search != map.end()) { + return search->second; + } + + // Default to dark theme + return Theme::Dark; +} + +const char* FrontendSettings::themeToString(Theme theme) { + switch (theme) { + case Theme::System: return "system"; + case Theme::Light: return "light"; + case Theme::GreetingsCat: return "greetingscat"; + case Theme::Cream: return "cream"; + + case Theme::Dark: + default: return "dark"; + } +} + +FrontendSettings::WindowIcon FrontendSettings::iconFromString(std::string inString) { // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + static const std::unordered_map map = { + {"rpog", WindowIcon::Rpog}, {"rsyn", WindowIcon::Rsyn}, + }; + + if (auto search = map.find(inString); search != map.end()) { + return search->second; + } + + // Default to the icon rpog icon + return WindowIcon::Rpog; +} + +const char* FrontendSettings::iconToString(WindowIcon icon) { + switch (icon) { + case WindowIcon::Rsyn: return "rsyn"; + + case WindowIcon::Rpog: + default: return "rpog"; + } +} \ No newline at end of file diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index eb5d25f5b..3fe89be1e 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -8,8 +8,8 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallb updateIcon = std::move(iconCallback); // Set up theme selection - setTheme(Theme::Dark); - setIcon(WindowIcon::Rpog); + setTheme(config.frontendSettings.theme); + setIcon(config.frontendSettings.icon); // Initialize the widget list and the widget container widgets widgetList = new QListWidget(this); @@ -65,17 +65,25 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallb themeSelect->addItem(tr("Dark")); themeSelect->addItem(tr("Greetings Cat")); themeSelect->addItem(tr("Cream")); - themeSelect->setCurrentIndex(static_cast(currentTheme)); + themeSelect->setCurrentIndex(static_cast(config.frontendSettings.theme)); connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { + config.frontendSettings.theme = static_cast(index); setTheme(static_cast(index)); + + updateConfig(); }); guiLayout->addRow(tr("Color theme"), themeSelect); QComboBox* iconSelect = new QComboBox(); iconSelect->addItem(tr("Happy panda")); iconSelect->addItem(tr("Happy panda (colourful)")); - iconSelect->setCurrentIndex(static_cast(currentIcon)); - connect(iconSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setIcon(static_cast(index)); }); + iconSelect->setCurrentIndex(static_cast(config.frontendSettings.icon)); + connect(iconSelect, &QComboBox::currentIndexChanged, this, [&](int index) { + config.frontendSettings.icon = static_cast(index); + setIcon(static_cast(index)); + + updateConfig(); + }); guiLayout->addRow(tr("Window icon"), iconSelect); QCheckBox* showAppVersion = new QCheckBox(tr("Show version on window title")); @@ -286,8 +294,6 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallb } void ConfigWindow::setTheme(Theme theme) { - currentTheme = theme; - switch (theme) { case Theme::Dark: { QApplication::setStyle(QStyleFactory::create("Fusion")); diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index afca88322..fa3efae7f 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -420,6 +420,9 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::UpdateConfig: emu->getConfig() = configWindow->getConfig(); emu->reloadSettings(); + + // Save new settings to disk + emu->getConfig().save(); break; } }