From 156328fbfbb86f75e0f7de8901a4dc2c6c806dae Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Sun, 1 Dec 2024 23:06:47 +0200 Subject: [PATCH 1/5] [WIP] Qt: Add config window controls (#655) * Qt: Add config window controls * Fix Windows build * Fix audio slider * Qt configs: Make thread-safe, properly update audio enable & renderdoc settings * Qt configs: Add `connectCheckbox` function * Qt configs: Add `connectCheckbox` function * Rename spuLayout * Add Discord RPC reloading * Allow configuring the app icon * Qt: Serialize icon & theme, properly set them * Add rnap and rcow icons * Qt: Fix forceShadergen config --------- Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> --- CMakeLists.txt | 6 +- docs/img/battery_icon.png | Bin 0 -> 4395 bytes docs/img/display_icon.png | Bin 0 -> 166 bytes docs/img/rcow_icon.png | Bin 0 -> 32878 bytes docs/img/rnap_icon.png | Bin 0 -> 27223 bytes docs/img/sdcard_icon.png | Bin 0 -> 4400 bytes docs/img/settings_icon.png | Bin 0 -> 697 bytes docs/img/sparkling_icon.png | Bin 0 -> 549 bytes docs/img/speaker_icon.png | Bin 0 -> 507 bytes include/config.hpp | 4 +- include/discord_rpc.hpp | 2 + include/emulator.hpp | 3 + include/frontend_settings.hpp | 32 +++ include/panda_qt/config_window.hpp | 48 ++++- include/panda_qt/main_window.hpp | 1 + include/renderdoc.hpp | 4 + src/config.cpp | 13 ++ src/emulator.cpp | 21 ++ src/frontend_settings.cpp | 64 ++++++ src/panda_qt/config_window.cpp | 312 ++++++++++++++++++++++++++++- src/panda_qt/main_window.cpp | 18 +- src/renderdoc.cpp | 6 + 22 files changed, 509 insertions(+), 25 deletions(-) create mode 100644 docs/img/battery_icon.png create mode 100644 docs/img/display_icon.png create mode 100644 docs/img/rcow_icon.png create mode 100644 docs/img/rnap_icon.png create mode 100644 docs/img/sdcard_icon.png create mode 100644 docs/img/settings_icon.png create mode 100644 docs/img/sparkling_icon.png create mode 100644 docs/img/speaker_icon.png create mode 100644 include/frontend_settings.hpp create mode 100644 src/frontend_settings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ca834f..33faba5f 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( @@ -695,6 +696,9 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) PREFIX "/" FILES docs/img/rsob_icon.png docs/img/rstarstruck_icon.png docs/img/rpog_icon.png docs/img/rsyn_icon.png + docs/img/settings_icon.png docs/img/display_icon.png docs/img/speaker_icon.png + docs/img/sparkling_icon.png docs/img/battery_icon.png docs/img/sdcard_icon.png + docs/img/rnap_icon.png docs/img/rcow_icon.png ) else() set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp src/panda_sdl/mappings.cpp) diff --git a/docs/img/battery_icon.png b/docs/img/battery_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5768a928078c9e12c03339b69d960bc84540bdb2 GIT binary patch literal 4395 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 bC5gEOxb;-3POxBLU|{fc^>bP0l+XkK=M$B= literal 0 HcmV?d00001 diff --git a/docs/img/rnap_icon.png b/docs/img/rnap_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7967102b26a114a14c25973ae16bb0285a439696 GIT binary patch literal 27223 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 AlK=n! literal 0 HcmV?d00001 diff --git a/docs/img/sdcard_icon.png b/docs/img/sdcard_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..07ed3fceb45717a2376442cb40773b4fe8140c94 GIT binary patch literal 4400 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJoE~G|yrg?g5F>o+2FmN$2 zFtRW(g48fDFfbyqnHZTFm>C!t+87uZgczB@YC!6w7+JyW9tH*mX()RN0|SEwR1FgY z1A`$W69X>;1H&8!1_mQWCI%4(28LA(3=Em=5WDscOLt;q2j#0)H3_%L;-@CJMdx=F&~t^Pxii`Q74*_jV2|rq4_( zJ;LC-{cMPmUfQPBN!PWVI2Oq7oiSl%>KV@gP&NeN7Q|t9Y5`$s#Xqu}Ya9|7lk? z=NP9z4code)Ak3kr`db1-@4_5puq;-n3(M*sr)^nUk#C56lsTcvPQ zUjyF)=hTc$kE){7;3~h6MhJ1(0FtBTx$+|-gpg^Jvqyke^gTP3i$R(Zu% zAYpwa1+bEmY+I!W-v9;Y{GwC^6Fn0>16|jO%rYY-J1zyAqLehNAQv~N5k)C!wn`Z# zB?VUc`sL;2dgaD?`9nIMXEJ)Q4N-fSWElN&xElbTSQARc*B^j>2ptL9l?46Wk{nVV)+|<01VtqqB zLli4PSq`oP6a?v+xdmWd6}bf{DoZlc5P^l_8;}gxJID^H$Sr`Ii=rClcd%k`7+JaG zCzpbp=jmdr1hU^MB|kYc#R|-{OtmmcPDwP-wKO+1)ip6oHP=l{N=wvDGB!%GOtDNg zOENMGdD3kRlguF9V`I~a4W|EPg^A;Jp+VDKu%&w zT7FTkt&&e>UTFnFA|x|6HMk@Z6q<(S#>NI_M&^db#+K%mMy3cwVW~yMnfZAjGr@%? zSOyd=R{lkqsd*)dpmb`h1P>@H=c3falKi5O{QMkSC6JTA#V%M|MQ(wWZ)!21szu;ly6H+Yg`Lc$Af#Hm&i(`n#@wZbqaxyszxc;AajIF7WcSc8eQ$$lI zYsNC(h{i)p*`_5hmn~FC%3 z0__LV*)$dhhyGwX@#d4%jal(b)}Mk44ofy`glX=O&z*Ow% z;uzxLy>(i2hi{_D@%s9u0-81z1)QFf($C9HxigEi^Tblk?NTigvaZK+J7#KhWoA8@ zbRyT}>B+_Fp7TyDnV`g#`D9zo>o2_3+xDKfTYRqS*6%l8YyWS(_J991t3}6_N!|B% zWiXazTEklBVKtrS*qLk0%?*5>@t>K_vKnk=dthN8^n=rcS^Uw`?1sA^e==|I4N4dD zP-~bO%QWpc^ZACAmV)0HCO_U@<|!9qf0}s*uMP{Jm)6Wk*#eFka{l&wsoI{qlGAh# z_&wOQMD;4u95yrN*D9qULQXnMKVMeL4ByJY-w=6#$+MiB!DhGgfm+Ya+>B>(3=4RF z@bpakvX|k&)NgtmNfrV6FViml=Vwpb#udSMrkAgGlHbqGGk3;oPCHwlHMwNRdnM!B zQdi21_TBHQ+41P#x5k+}mDF69D^HpwBtEA=bD2@*v@PZjFLUK|zPFvUYzbo(qknnL zgxjk_g7>n!F&Z~*Vq@-axIXFF(m!fU`VDcOhd(i>c~s4``oSh49W-B(QBI7ZX5y1M zDbr3ksl_d_?fkO$-o2fkxAimQ{Uqmm&6>tmFljpD=f?|sC!JICD05i<(R_ia%ZubC zIceK$`$X&CGOhF2b%S5w;U1A4Ts2I`LNqULKfsYNvAo>q?4H0v{u#n2)=Yk~>GW2Y z`z7A9rk9;z&R~{mWb=H>${^ifeBh$Nw#zoD@0pEX_wimgfAgx5IdECucb1GvS5ni? zwH6yJNPNEeu4j3*+&71vnMOL6>#SARu3P02D7kNApX;Z($|Iahe^ivctm?F9vUa*W z_k7oaj8s0>uxU@fy+3v5i7a=_zx7iiJ8v}Btp2&b>ptV9Pm*=rH`g;TFfe$!`njxg HN@xNAXel!+ literal 0 HcmV?d00001 diff --git a/docs/img/sparkling_icon.png b/docs/img/sparkling_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4a46d8d8d80a8c28a7c708b323933dce16d8526b GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4rT@hhU+WOo?>8NU@Q)DcVbv~PUa;81H;|` zpAc6D28RDb7Ianb%VA((uqz4j3ufTplvPqzQB_md)Y3IDG&ZrYw6?LecX09W@+m8? zu3fxj+42=DSFK*Ve#6GiJNNF}f8fZSyZ7!tc=zG+m%slkohBV&U|=}o>EaloalUub zO+O|_0oVL)CS#*D=M;E2x^nb#^mu47rd$+4=HPv$Lo6_)4ue{IoAo*6xV zs(5tIw$&`C!XsW z#I(^_%$|MOv^npsLR4C_86w*Er+m|}=UCsjH~C;$ji>3tm*+n`ed+V!`qBOSpE3v^ XlR2^U^%FS;1_lOCS3j3^P68NU@Q)DcVbv~PUa;81H+a8 zpAc6D28RDb4ZP6ar^vv-U|kaA7tFxO!p6bN$1fl#BrGPcqM>8p>Fpa46C0nDl98F6 zlbc^yR9d%o2jLY$#SLr#%) zZSmP<>;c>eUXW7A)w8pQsbmLx)|F%hf9`;a ze#Ta_36GW8q-+>M;}$C{-rdi?qv(Jt$FDm}?e6pHTj=k2e2<&!>ijy@RVVY3I@9we mWzV1C^&}wb)55xI+}97PpFF7a#+ZSDfx*+&&t;ucLK6TlP%=LN literal 0 HcmV?d00001 diff --git a/include/config.hpp b/include/config.hpp index 5a7c7fff..7361a40d 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/discord_rpc.hpp b/include/discord_rpc.hpp index 9b244faf..62bd0c6b 100644 --- a/include/discord_rpc.hpp +++ b/include/discord_rpc.hpp @@ -17,6 +17,8 @@ namespace Discord { void init(); void update(RPCStatus status, const std::string& title); void stop(); + + bool running() const { return enabled; } }; } // namespace Discord diff --git a/include/emulator.hpp b/include/emulator.hpp index abb74089..cf231328 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -118,6 +118,9 @@ class Emulator { void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } void deinitGraphicsContext() { gpu.deinitGraphicsContext(); } + // Reloads some settings that require special handling, such as audio enable + void reloadSettings(); + EmulatorConfig& getConfig() { return config; } Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } diff --git a/include/frontend_settings.hpp b/include/frontend_settings.hpp new file mode 100644 index 00000000..aaf9eaf0 --- /dev/null +++ b/include/frontend_settings.hpp @@ -0,0 +1,32 @@ +#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, + Rnap = 2, + Rcow = 3, + }; + + 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 4a523879..3cf4a1c8 100644 --- a/include/panda_qt/config_window.hpp +++ b/include/panda_qt/config_window.hpp @@ -1,30 +1,56 @@ #pragma once #include +#include #include #include +#include #include +#include +#include #include #include +#include +#include +#include + +#include "emulator.hpp" +#include "frontend_settings.hpp" class ConfigWindow : public QDialog { Q_OBJECT private: - enum class Theme : int { - System = 0, - Light = 1, - Dark = 2, - GreetingsCat = 3, - Cream = 4, - }; + using ConfigCallback = std::function; + using IconCallback = std::function; - Theme currentTheme; - QComboBox* themeSelect = nullptr; + using Theme = FrontendSettings::Theme; + using WindowIcon = FrontendSettings::WindowIcon; - void setTheme(Theme theme); + QTextEdit* helpText = nullptr; + QListWidget* widgetList = nullptr; + QStackedWidget* widgetContainer = nullptr; + + static constexpr size_t settingWidgetCount = 6; + std::array helpTexts; + + // The config class holds a copy of the emulator config which it edits and sends + // over to the emulator in a thread-safe manner + EmulatorConfig config; + + ConfigCallback updateConfig; + IconCallback updateIcon; + + void addWidget(QWidget* widget, QString title, QString icon, QString helpText); + void setTheme(FrontendSettings::Theme theme); + void setIcon(FrontendSettings::WindowIcon icon); public: - ConfigWindow(QWidget* parent = nullptr); + ConfigWindow(ConfigCallback configCallback, IconCallback iconCallback, const EmulatorConfig& config, QWidget* parent = nullptr); ~ConfigWindow(); + + EmulatorConfig& getConfig() { return config; } + + private: + Emulator* emu; }; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index eb6b30e0..eb1cfb16 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -51,6 +51,7 @@ class MainWindow : public QMainWindow { ReleaseTouchscreen, ReloadUbershader, SetScreenSize, + UpdateConfig, }; // Tagged union representing our message queue messages diff --git a/include/renderdoc.hpp b/include/renderdoc.hpp index 02f2ade4..9c7de1e3 100644 --- a/include/renderdoc.hpp +++ b/include/renderdoc.hpp @@ -23,6 +23,9 @@ namespace Renderdoc { // Sets output directory for captures void setOutputDir(const std::string& path, const std::string& prefix); + // Returns whether Renderdoc has been loaded + bool isLoaded(); + // Returns whether we've compiled with Renderdoc support static constexpr bool isSupported() { return true; } } // namespace Renderdoc @@ -34,6 +37,7 @@ namespace Renderdoc { static void triggerCapture() { Helpers::panic("Tried to trigger a Renderdoc capture while support for renderdoc is disabled"); } static void setOutputDir(const std::string& path, const std::string& prefix) {} static constexpr bool isSupported() { return false; } + static constexpr bool isLoaded() { return false; } } // namespace Renderdoc #endif diff --git a/src/config.cpp b/src/config.cpp index 93aed106..a8c88a68 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/emulator.cpp b/src/emulator.cpp index fc25eacb..1bb117b5 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -444,3 +444,24 @@ void Emulator::loadRenderdoc() { Renderdoc::loadRenderdoc(); Renderdoc::setOutputDir(capturePath, ""); } + +void Emulator::reloadSettings() { + setAudioEnabled(config.audioEnabled); + + if (Renderdoc::isSupported() && config.enableRenderdoc && !Renderdoc::isLoaded()) { + loadRenderdoc(); + } + +#ifdef PANDA3DS_ENABLE_DISCORD_RPC + // Reload RPC setting if we're compiling with RPC support + + if (discordRpc.running() != config.discordRpcEnabled) { + if (config.discordRpcEnabled) { + discordRpc.init(); + updateDiscord(); + } else { + discordRpc.stop(); + } + } +#endif +} \ No newline at end of file diff --git a/src/frontend_settings.cpp b/src/frontend_settings.cpp new file mode 100644 index 00000000..16bae361 --- /dev/null +++ b/src/frontend_settings.cpp @@ -0,0 +1,64 @@ +#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}, + {"rcow", WindowIcon::Rcow}, + {"rnap", WindowIcon::Rnap}, + }; + + 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::Rcow: return "rcow"; + case WindowIcon::Rnap: return "rnap"; + + 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 75293742..64cade49 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -1,26 +1,286 @@ #include "panda_qt/config_window.hpp" -ConfigWindow::ConfigWindow(QWidget* parent) : QDialog(parent) { +ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallback, const EmulatorConfig& emuConfig, QWidget* parent) + : QDialog(parent), config(emuConfig) { setWindowTitle(tr("Configuration")); + updateConfig = std::move(configCallback); + updateIcon = std::move(iconCallback); + // Set up theme selection - setTheme(Theme::Dark); - themeSelect = new QComboBox(this); + setTheme(config.frontendSettings.theme); + setIcon(config.frontendSettings.icon); + + // Initialize the widget list and the widget container widgets + widgetList = new QListWidget(this); + widgetContainer = new QStackedWidget(this); + + helpText = new QTextEdit(this); + helpText->setReadOnly(true); + + helpText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + helpText->setFixedHeight(50); + + widgetList->setMinimumWidth(100); + widgetList->setMaximumWidth(100); + widgetList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + widgetList->setPalette(QPalette(QColor(25, 25, 25))); + + widgetList->setCurrentRow(0); + widgetContainer->setCurrentIndex(0); + + connect(widgetList, &QListWidget::currentRowChanged, this, [&](int row) { + widgetContainer->setCurrentIndex(row); + helpText->setText(helpTexts[row]); + }); + + auto connectCheckbox = [&](QCheckBox* checkbox, bool& setting) { + checkbox->setChecked(setting); + + connect(checkbox, &QCheckBox::toggled, this, [&](bool checked) { + setting = checked; + updateConfig(); + }); + }; + + QVBoxLayout* mainLayout = new QVBoxLayout(); + QHBoxLayout* hLayout = new QHBoxLayout(); + + // Set up widget layouts + setLayout(mainLayout); + mainLayout->addLayout(hLayout); + mainLayout->addWidget(helpText); + + hLayout->setAlignment(Qt::AlignLeft); + hLayout->addWidget(widgetList); + hLayout->addWidget(widgetContainer); + + // Interface settings + QGroupBox* guiGroupBox = new QGroupBox(tr("Interface Settings"), this); + QFormLayout* guiLayout = new QFormLayout(guiGroupBox); + guiLayout->setHorizontalSpacing(20); + guiLayout->setVerticalSpacing(10); + + QComboBox* themeSelect = new QComboBox(); themeSelect->addItem(tr("System")); themeSelect->addItem(tr("Light")); 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)); - themeSelect->setGeometry(40, 40, 100, 50); - themeSelect->show(); - connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int 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->addItem(tr("Sleepy panda")); + iconSelect->addItem(tr("Cow panda")); + 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")); + connectCheckbox(showAppVersion, config.windowSettings.showAppVersion); + guiLayout->addRow(showAppVersion); + + QCheckBox* rememberPosition = new QCheckBox(tr("Remember window position")); + connectCheckbox(rememberPosition, config.windowSettings.rememberPosition); + guiLayout->addRow(rememberPosition); + + // General settings + QGroupBox* genGroupBox = new QGroupBox(tr("General Settings"), this); + QFormLayout* genLayout = new QFormLayout(genGroupBox); + genLayout->setHorizontalSpacing(20); + genLayout->setVerticalSpacing(10); + + QLineEdit* defaultRomPath = new QLineEdit; + defaultRomPath->setText(QString::fromStdU16String(config.defaultRomPath.u16string())); + connect(defaultRomPath, &QLineEdit::textChanged, this, [&](const QString& text) { + config.defaultRomPath = text.toStdString(); + updateConfig(); + }); + QPushButton* browseRomPath = new QPushButton(tr("Browse...")); + browseRomPath->setAutoDefault(false); + connect(browseRomPath, &QPushButton::pressed, this, [&, defaultRomPath]() { + QString newPath = QFileDialog::getExistingDirectory( + this, tr("Select Directory"), QString::fromStdU16String(config.defaultRomPath.u16string()), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + if (!newPath.isEmpty()) { + defaultRomPath->setText(newPath); + } + }); + QHBoxLayout* romLayout = new QHBoxLayout; + romLayout->setSpacing(4); + romLayout->addWidget(defaultRomPath); + romLayout->addWidget(browseRomPath); + genLayout->addRow(tr("Default ROMs path"), romLayout); + + QCheckBox* discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC")); + connectCheckbox(discordRpcEnabled, config.discordRpcEnabled); + genLayout->addRow(discordRpcEnabled); + + QCheckBox* usePortableBuild = new QCheckBox(tr("Use portable build")); + connectCheckbox(usePortableBuild, config.usePortableBuild); + genLayout->addRow(usePortableBuild); + + QCheckBox* printAppVersion = new QCheckBox(tr("Print version in console output")); + connectCheckbox(printAppVersion, config.printAppVersion); + genLayout->addRow(printAppVersion); + + // Graphics settings + QGroupBox* gpuGroupBox = new QGroupBox(tr("Graphics Settings"), this); + QFormLayout* gpuLayout = new QFormLayout(gpuGroupBox); + gpuLayout->setHorizontalSpacing(20); + gpuLayout->setVerticalSpacing(10); + + QComboBox* rendererType = new QComboBox; + rendererType->addItem(tr("Null")); + rendererType->addItem(tr("OpenGL")); + rendererType->addItem(tr("Vulkan")); + rendererType->setCurrentIndex(static_cast(config.rendererType)); + connect(rendererType, &QComboBox::currentIndexChanged, this, [&](int index) { + config.rendererType = static_cast(index); + updateConfig(); + }); + gpuLayout->addRow(tr("GPU renderer"), rendererType); + + QCheckBox* enableRenderdoc = new QCheckBox(tr("Enable Renderdoc")); + connectCheckbox(enableRenderdoc, config.enableRenderdoc); + gpuLayout->addRow(enableRenderdoc); + + QCheckBox* shaderJitEnabled = new QCheckBox(tr("Enable shader JIT")); + connectCheckbox(shaderJitEnabled, config.shaderJitEnabled); + gpuLayout->addRow(shaderJitEnabled); + + QCheckBox* vsyncEnabled = new QCheckBox(tr("Enable VSync")); + connectCheckbox(vsyncEnabled, config.vsyncEnabled); + gpuLayout->addRow(vsyncEnabled); + + QCheckBox* useUbershaders = new QCheckBox(tr("Use ubershaders (No stutter, maybe slower)")); + connectCheckbox(useUbershaders, config.useUbershaders); + gpuLayout->addRow(useUbershaders); + + QCheckBox* accurateShaderMul = new QCheckBox(tr("Accurate shader multiplication")); + connectCheckbox(accurateShaderMul, config.accurateShaderMul); + gpuLayout->addRow(accurateShaderMul); + + QCheckBox* accelerateShaders = new QCheckBox(tr("Accelerate shaders")); + connectCheckbox(accelerateShaders, config.accelerateShaders); + gpuLayout->addRow(accelerateShaders); + + QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights")); + connectCheckbox(forceShadergenForLights, config.forceShadergenForLights); + gpuLayout->addRow(forceShadergenForLights); + + QSpinBox* lightShadergenThreshold = new QSpinBox; + lightShadergenThreshold->setRange(1, 8); + lightShadergenThreshold->setValue(config.lightShadergenThreshold); + connect(lightShadergenThreshold, &QSpinBox::valueChanged, this, [&](int value) { + config.lightShadergenThreshold = static_cast(value); + updateConfig(); + }); + gpuLayout->addRow(tr("Light threshold for forcing shadergen"), lightShadergenThreshold); + + // Audio settings + QGroupBox* spuGroupBox = new QGroupBox(tr("Audio Settings"), this); + QFormLayout* audioLayout = new QFormLayout(spuGroupBox); + audioLayout->setHorizontalSpacing(20); + audioLayout->setVerticalSpacing(10); + + QComboBox* dspType = new QComboBox; + dspType->addItem(tr("Null")); + dspType->addItem(tr("LLE")); + dspType->addItem(tr("HLE")); + dspType->setCurrentIndex(static_cast(config.dspType)); + connect(dspType, &QComboBox::currentIndexChanged, this, [&](int index) { + config.dspType = static_cast(index); + updateConfig(); + }); + audioLayout->addRow(tr("DSP emulation"), dspType); + + QCheckBox* audioEnabled = new QCheckBox(tr("Enable audio")); + connectCheckbox(audioEnabled, config.audioEnabled); + audioLayout->addRow(audioEnabled); + + QCheckBox* aacEnabled = new QCheckBox(tr("Enable AAC audio")); + connectCheckbox(aacEnabled, config.aacEnabled); + audioLayout->addRow(aacEnabled); + + QCheckBox* printDSPFirmware = new QCheckBox(tr("Print DSP firmware")); + connectCheckbox(printDSPFirmware, config.printDSPFirmware); + audioLayout->addRow(printDSPFirmware); + + QCheckBox* muteAudio = new QCheckBox(tr("Mute audio device")); + connectCheckbox(muteAudio, config.audioDeviceConfig.muteAudio); + audioLayout->addRow(muteAudio); + + QSpinBox* volumeRaw = new QSpinBox(); + volumeRaw->setRange(0, 200); + volumeRaw->setValue(config.audioDeviceConfig.volumeRaw* 100); + connect(volumeRaw, &QSpinBox::valueChanged, this, [&](int value) { + config.audioDeviceConfig.volumeRaw = static_cast(value) / 100.0f; + updateConfig(); + }); + audioLayout->addRow(tr("Audio device volume"), volumeRaw); + + // Battery settings + QGroupBox* batGroupBox = new QGroupBox(tr("Battery Settings"), this); + QFormLayout* batLayout = new QFormLayout(batGroupBox); + batLayout->setHorizontalSpacing(20); + batLayout->setVerticalSpacing(10); + + QSpinBox* batteryPercentage = new QSpinBox; + batteryPercentage->setRange(1, 100); + batteryPercentage->setValue(config.batteryPercentage); + connect(batteryPercentage, &QSpinBox::valueChanged, this, [&](int value) { + config.batteryPercentage = static_cast(value); + updateConfig(); + }); + batLayout->addRow(tr("Battery percentage"), batteryPercentage); + + QCheckBox* chargerPlugged = new QCheckBox(tr("Charger plugged")); + connectCheckbox(chargerPlugged, config.chargerPlugged); + batLayout->addRow(chargerPlugged); + + // SD Card settings + QGroupBox* sdcGroupBox = new QGroupBox(tr("SD Card Settings"), this); + QFormLayout* sdcLayout = new QFormLayout(sdcGroupBox); + sdcLayout->setHorizontalSpacing(20); + sdcLayout->setVerticalSpacing(10); + + QCheckBox* sdCardInserted = new QCheckBox(tr("Enable virtual SD card")); + connectCheckbox(sdCardInserted, config.sdCardInserted); + sdcLayout->addRow(sdCardInserted); + + QCheckBox* sdWriteProtected = new QCheckBox(tr("Write protect virtual SD card")); + connectCheckbox(sdWriteProtected, config.sdWriteProtected); + sdcLayout->addRow(sdWriteProtected); + + // Add all our settings widgets to our widget list + addWidget(guiGroupBox, tr("Interface"), ":/docs/img/sparkling_icon.png", tr("User Interface settings")); + addWidget(genGroupBox, tr("General"), ":/docs/img/settings_icon.png", tr("General emulator settings")); + addWidget(gpuGroupBox, tr("Graphics"), ":/docs/img/display_icon.png", tr("Graphics emulation and output settings")); + addWidget(spuGroupBox, tr("Audio"), ":/docs/img/speaker_icon.png", tr("Audio emulation and output settings")); + addWidget(batGroupBox, tr("Battery"), ":/docs/img/battery_icon.png", tr("Battery emulation settings")); + addWidget(sdcGroupBox, tr("SD Card"), ":/docs/img/sdcard_icon.png", tr("SD Card emulation settings")); + + widgetList->setCurrentRow(0); } void ConfigWindow::setTheme(Theme theme) { - currentTheme = theme; - switch (theme) { case Theme::Dark: { QApplication::setStyle(QStyleFactory::create("Fusion")); @@ -119,4 +379,36 @@ void ConfigWindow::setTheme(Theme theme) { } } -ConfigWindow::~ConfigWindow() { delete themeSelect; } +void ConfigWindow::setIcon(WindowIcon icon) { + switch (icon) { + case WindowIcon::Rsyn: updateIcon(":/docs/img/rsyn_icon.png"); break; + case WindowIcon::Rnap: updateIcon(":/docs/img/rnap_icon.png"); break; + case WindowIcon::Rcow: updateIcon(":/docs/img/rcow_icon.png"); break; + + case WindowIcon::Rpog: + default: updateIcon(":/docs/img/rpog_icon.png"); break; + } +} + +void ConfigWindow::addWidget(QWidget* widget, QString title, QString icon, QString helpText) { + const int index = widgetList->count(); + + QListWidgetItem* item = new QListWidgetItem(widgetList); + item->setText(title); + if (!icon.isEmpty()) { + item->setIcon(QIcon::fromTheme(icon)); + } + + widgetContainer->addWidget(widget); + + if (index >= settingWidgetCount) { + Helpers::panic("Qt: ConfigWindow::settingWidgetCount has not been updated correctly!"); + } + helpTexts[index] = std::move(helpText); +} + +ConfigWindow::~ConfigWindow() { + delete helpText; + delete widgetList; + delete widgetContainer; +} diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 93ce2613..fa3efae7 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -15,7 +15,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) { setWindowTitle("Alber"); - setWindowIcon(QIcon(":/docs/img/rpog_icon.png")); // Enable drop events for loading ROMs setAcceptDrops(true); @@ -81,7 +80,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // Set up misc objects aboutWindow = new AboutWindow(nullptr); - configWindow = new ConfigWindow(this); cheatsEditor = new CheatsWindow(emu, {}, this); patchWindow = new PatchWindow(this); luaEditor = new TextEditorWindow(this, "script.lua", ""); @@ -92,6 +90,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) shaderEditor->setText(emu->getRenderer()->getUbershader()); } + configWindow = new ConfigWindow( + [&]() { + EmulatorMessage message{.type = MessageType::UpdateConfig}; + sendMessage(message); + }, + [&](const QString& icon) { setWindowIcon(QIcon(icon)); }, emu->getConfig(), this + ); + auto args = QCoreApplication::arguments(); if (args.size() > 1) { auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String(); @@ -410,6 +416,14 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { screen->resizeSurface(width, height); break; } + + case MessageType::UpdateConfig: + emu->getConfig() = configWindow->getConfig(); + emu->reloadSettings(); + + // Save new settings to disk + emu->getConfig().save(); + break; } } diff --git a/src/renderdoc.cpp b/src/renderdoc.cpp index 1de9c451..43627b66 100644 --- a/src/renderdoc.cpp +++ b/src/renderdoc.cpp @@ -23,6 +23,8 @@ namespace Renderdoc { }; static CaptureState captureState{CaptureState::Idle}; + static bool renderdocLoaded{false}; + RENDERDOC_API_1_6_0* rdocAPI{}; void loadRenderdoc() { @@ -73,6 +75,8 @@ namespace Renderdoc { } #endif if (rdocAPI) { + renderdocLoaded = true; + // Disable default capture keys as they suppose to trigger present-to-present capturing // and it is not what we want rdocAPI->SetCaptureKeys(nullptr, 0); @@ -115,5 +119,7 @@ namespace Renderdoc { rdocAPI->SetCaptureFilePathTemplate((path + '\\' + prefix).c_str()); } } + + bool isLoaded() { return renderdocLoaded; } } // namespace Renderdoc #endif \ No newline at end of file From c3e3b2358da4e80e1a810bcee7a39093b259c5a4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:38:20 +0200 Subject: [PATCH 2/5] Qt: Fix "Show app version on window" option --- include/panda_qt/config_window.hpp | 6 +++--- src/panda_qt/config_window.cpp | 26 ++++++++++++++++++++------ src/panda_qt/main_window.cpp | 13 ++++--------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/include/panda_qt/config_window.hpp b/include/panda_qt/config_window.hpp index 3cf4a1c8..9c003753 100644 --- a/include/panda_qt/config_window.hpp +++ b/include/panda_qt/config_window.hpp @@ -22,7 +22,7 @@ class ConfigWindow : public QDialog { private: using ConfigCallback = std::function; - using IconCallback = std::function; + using MainWindowCallback = std::function; using Theme = FrontendSettings::Theme; using WindowIcon = FrontendSettings::WindowIcon; @@ -39,14 +39,14 @@ class ConfigWindow : public QDialog { EmulatorConfig config; ConfigCallback updateConfig; - IconCallback updateIcon; + MainWindowCallback getMainWindow; void addWidget(QWidget* widget, QString title, QString icon, QString helpText); void setTheme(FrontendSettings::Theme theme); void setIcon(FrontendSettings::WindowIcon icon); public: - ConfigWindow(ConfigCallback configCallback, IconCallback iconCallback, const EmulatorConfig& config, QWidget* parent = nullptr); + ConfigWindow(ConfigCallback configCallback, MainWindowCallback windowCallback, const EmulatorConfig& config, QWidget* parent = nullptr); ~ConfigWindow(); EmulatorConfig& getConfig() { return config; } diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index 64cade49..92fa0741 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -1,16 +1,20 @@ #include "panda_qt/config_window.hpp" -ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallback, const EmulatorConfig& emuConfig, QWidget* parent) - : QDialog(parent), config(emuConfig) { - setWindowTitle(tr("Configuration")); +#include "version.hpp" - updateConfig = std::move(configCallback); - updateIcon = std::move(iconCallback); +ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback windowCallback, const EmulatorConfig& emuConfig, QWidget* parent) + : QDialog(parent), config(emuConfig), updateConfig(std::move(configCallback)), getMainWindow(std::move(windowCallback)) { + setWindowTitle(tr("Configuration")); // Set up theme selection setTheme(config.frontendSettings.theme); setIcon(config.frontendSettings.icon); + // Set the window title of the main window appropriately if we enable showing the app version on the window + if (config.windowSettings.showAppVersion) { + getMainWindow()->setWindowTitle("Alber v" PANDA3DS_VERSION); + } + // Initialize the widget list and the widget container widgets widgetList = new QListWidget(this); widgetContainer = new QStackedWidget(this); @@ -92,6 +96,14 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallb guiLayout->addRow(tr("Window icon"), iconSelect); QCheckBox* showAppVersion = new QCheckBox(tr("Show version on window title")); + showAppVersion->setChecked(config.windowSettings.showAppVersion); + connect(showAppVersion, &QCheckBox::toggled, this, [&](bool checked) { + config.windowSettings.showAppVersion = checked; + updateConfig(); + + // Update main window title + getMainWindow()->setWindowTitle(checked ? "Alber v" PANDA3DS_VERSION : "Alber"); + }); connectCheckbox(showAppVersion, config.windowSettings.showAppVersion); guiLayout->addRow(showAppVersion); @@ -229,7 +241,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, IconCallback iconCallb QSpinBox* volumeRaw = new QSpinBox(); volumeRaw->setRange(0, 200); - volumeRaw->setValue(config.audioDeviceConfig.volumeRaw* 100); + volumeRaw->setValue(config.audioDeviceConfig.volumeRaw * 100); connect(volumeRaw, &QSpinBox::valueChanged, this, [&](int value) { config.audioDeviceConfig.volumeRaw = static_cast(value) / 100.0f; updateConfig(); @@ -380,6 +392,8 @@ void ConfigWindow::setTheme(Theme theme) { } void ConfigWindow::setIcon(WindowIcon icon) { + auto updateIcon = [&](const QString& iconPath) { getMainWindow()->setWindowIcon(QIcon(iconPath)); }; + switch (icon) { case WindowIcon::Rsyn: updateIcon(":/docs/img/rsyn_icon.png"); break; case WindowIcon::Rnap: updateIcon(":/docs/img/rnap_icon.png"); break; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index fa3efae7..2c714e2a 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -7,11 +7,11 @@ #include #include -#include "version.hpp" #include "cheats.hpp" #include "input_mappings.hpp" #include "sdl_sensors.hpp" #include "services/dsp.hpp" +#include "version.hpp" MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) { setWindowTitle("Alber"); @@ -95,7 +95,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) EmulatorMessage message{.type = MessageType::UpdateConfig}; sendMessage(message); }, - [&](const QString& icon) { setWindowIcon(QIcon(icon)); }, emu->getConfig(), this + [&]() { return this; }, emu->getConfig(), this ); auto args = QCoreApplication::arguments(); @@ -112,10 +112,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto& config = emu->getConfig(); auto& windowSettings = config.windowSettings; - if (windowSettings.showAppVersion) { - setWindowTitle("Alber v" PANDA3DS_VERSION); - } - if (windowSettings.rememberPosition) { setGeometry(windowSettings.x, windowSettings.y, windowSettings.width, config.windowSettings.height); } @@ -236,7 +232,7 @@ void MainWindow::selectLuaFile() { } // Stop emulator thread when the main window closes -void MainWindow::closeEvent(QCloseEvent *event) { +void MainWindow::closeEvent(QCloseEvent* event) { appRunning = false; // Set our running atomic to false in order to make the emulator thread stop, and join it if (emuThread.joinable()) { @@ -319,8 +315,7 @@ void MainWindow::dumpDspFirmware() { case DSPService::ComponentDumpResult::Success: break; case DSPService::ComponentDumpResult::NotLoaded: { QMessageBox messageBox( - QMessageBox::Icon::Warning, tr("No DSP firmware loaded"), - tr("The currently loaded app has not uploaded a firmware to the DSP") + QMessageBox::Icon::Warning, tr("No DSP firmware loaded"), tr("The currently loaded app has not uploaded a firmware to the DSP") ); QAbstractButton* button = messageBox.addButton(tr("OK"), QMessageBox::ButtonRole::YesRole); From d8df0dc4fac6093c6c9ed4860a12f6a871b6f493 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:46:18 +0200 Subject: [PATCH 3/5] Qt: Add title to ROM patcher --- src/panda_qt/patch_window.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panda_qt/patch_window.cpp b/src/panda_qt/patch_window.cpp index 189288eb..6ba73e84 100644 --- a/src/panda_qt/patch_window.cpp +++ b/src/panda_qt/patch_window.cpp @@ -12,6 +12,8 @@ #include "io_file.hpp" PatchWindow::PatchWindow(QWidget* parent) : QWidget(parent, Qt::Window) { + setWindowTitle("ROM patcher"); + QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(6, 6, 6, 6); setLayout(layout); @@ -155,4 +157,4 @@ void PatchWindow::PatchWindow::displayMessage(const QString& title, const QStrin } messageBox.exec(); -} \ No newline at end of file +} From 6b1f39bdf00768ff015c1fa40c75ae5e879a80e6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:49:19 +0200 Subject: [PATCH 4/5] Qt: Label cheat windows --- src/panda_qt/cheats_window.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index cc2c94f6..2d7e6b4c 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -69,6 +69,8 @@ void CheatEntryWidget::editClicked() { } CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) { + setWindowTitle("Edit Cheat"); + setAttribute(Qt::WA_DeleteOnClose); setModal(true); @@ -159,6 +161,7 @@ void CheatEditDialog::rejected() { CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) : QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) { + setWindowTitle("Cheats"); mainWindow = static_cast(parent); QVBoxLayout* layout = new QVBoxLayout; @@ -205,4 +208,4 @@ void CheatsWindow::removeClicked() { CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); entry->Remove(); -} \ No newline at end of file +} From 79a9de25d4b8d9ee30a30fc712d5064bb0c143f9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 2 Dec 2024 00:08:35 +0200 Subject: [PATCH 5/5] Qt: Label Lua Editor window --- src/panda_qt/text_editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp index a31a829f..24febd0d 100644 --- a/src/panda_qt/text_editor.cpp +++ b/src/panda_qt/text_editor.cpp @@ -9,6 +9,7 @@ using namespace Zep; TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText) : QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) { + setWindowTitle("Lua Editor"); resize(600, 600); // Register our extensions