From 156328fbfbb86f75e0f7de8901a4dc2c6c806dae Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Sun, 1 Dec 2024 23:06:47 +0200 Subject: [PATCH] [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 d3ca834fd..33faba5f6 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 zcmeHKYitzP72d@$>o}Mi%cZFUVi=Qz*X%s@J@VSv-VM7o3$DGvHX;o(vv=1M@5?*0 z-nA7f6Jj2PQXoz!!gWoQnnxnj(jd2WAa;0GB7wxIDo|ToPzq8(PE|+(v6`HjUB8-0 zk)`CHcXjukIrp6JocrB#k3HKfSFg;;F38qsG&ydUqYCCnm60_QeuonF&tSTYYTbUf zd#NT19>7xjkVdQ6z|c;RXJ|7t8KBz&BM)T7Mi~X#Om*G~@?&Ya9puGnn;9U}+8LT0 zm^)xFAm_t;6o$Ap73&np$|phwdnz$TPg8n>75pj2$kGf;(HL!JX)DW6 z5MSFho{@sIZ_eQxkYAfvUS8=gFUO*>h+hmMjV9T#agW_~x-!3QO||p=qnqbD<}uzU zcXZ*qPGma|=RViAcK)iLJkWUx_U8X!<-m!dp-cQ<2F}*i=xR!HN3@Mi(JFaQ-f%@W z!N2u!a>32NOUdEl6^Hlj_s7p@JiYIFalQZe>-=Arm1T7;IQh%n`_V;rRo&mJ)Hae%$~ciyj`X9m$;9ec=N&>>bE)T*Y_IH_x|uV z?Uv42SK6Oh;;OiCbJM4W2i?o5wa7EEx)hB{pftRa_Qi zMNQ(EY9cq-@meUP@<q}%8I(3iHU&w815wN z(Y>q2VCC_!j))Lf(sMiPxDucBMFi2usv^(x7RF|^5k8CANEii!K=5W{Bg|%#$xN9o zMhfv0pxj|e=E4F}pa5Ji0*=wl8~g?nLkL!IG%{8n!6D8-@D@QZAjI>6jhg_mCMH5v za>0pNDNsIuqKyVCZM6ynZ81PLw4WxdCf-k21;$KSOg^K*pMvrU>{F3gh=b!4LtFrn z(QrVGp%BiNR=Vvtqo>9-l|fGSgM%GkC5Ge4@dd9KLe(;-un92@l$o(m6l0={Cd0VV zS`?F@5*19fd8#YQv9PciAeK|=6aZ8`?1e3lAx@6Oypc%Ij*qq7v1JcjCm$zs4o*e@ zI@XB2qm6hO1${Y{z5?fo_(Xs6H(4u}2P*=5IC7ULLHuS_mAX;Ys4?}FdJKx{Rl+d! zQm~wm+JeM2A)lHjV5OFXdM+G5&^^-SI;t1HrW6F5&BRy@JV9|rD`7-NI6n(A6BK0- ztTr<;nJw1I=u*TlC%70Y4FHe86_ls?Ckk7v9@LV_)`@zgoB|L=&kq4t|>7vCFAMp`qt>m9)F!eVfZg70WVAcLyP|jFIqGCij@w{7s|Kmp?(3X^KM6}*LD6*`@-4ptjKj<%blm|-;3?& z&s%zUVAJW-n}!Nr>zLswTX8TechQFay?2GHuZhoHJ>GLE_(b3R62s?-gWcUrU(qkh z?0=~K<;yJ@2lwBR5|0y$oJY^qd|>U(a0j{`x!o#kpS`737kqd51;e)MS9QfJ`dUAm ze=B3*$C?)ZCl_}u<}k53YOHYo-n-EwKep?g!NyQ)!{!73uK48Q?aa%o=Ktvo{mjcd zk1RNL$We6Q)jtkyoqJ$stJrpo5Baw~(DmFWOVIjRTVLF^W;tw0<94og^ptIQ;Y%A_ B>$Cs> literal 0 HcmV?d00001 diff --git a/docs/img/display_icon.png b/docs/img/display_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf6a68beb1ab40b9f6ba4c372e3860bd2afbf8f7 GIT binary patch literal 166 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 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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 LYAIC9S%&-%?8k7E 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 zcmeHKeQ;FO72i#o1X5B%CR&U#JYS?h)3@(yvmdY7K$4Bw6&BKDO*SbN-pAd|EBnR1 zx7kfboF)(z@dF|iX=xhAih@ikRHPkEOsYWYAj;5A8>gBKR#8XADhvpj((~SKzFKEG znW_I}-`u_Do_o&kp8LD!p53>-c5O{wZc(nmV90a3oORGI(%QUi_#R6*u0Zz|^11zP z_xBC+U;viJZyAi54K(94Im4J~$OPSe&ggPpvfovi6y$KX#B7LYwGo+@4yDG0~KXwShmym^qr^C>oXrG8mHkw(N4aUac+cT326v=HS-F&RZGp zvK_tnBS&+qpUwYiSHt2pcVEr%6m=JVw`S<**w~xGUxto1HRd!{*=gL zSv@yf&K_txM;-RHh)+MY!LY(59k`Cm zfvl-X9MetIO%B`#iBuJdADYr6sc`jBKll)wr^NutGj2w82szy1IOe7Mf z1Z#@K0wis>+ewNc8HNB0LTL-De3A$&#TrBk!-*6zCP!5{62>%4UWmk12adx$HW6Pa z>hVm%hm|x7zz3P+qaT-59Jq9?lsN<63xbrvum4=cI@zOPns+kj%`8$t8PMwkT)P1q$XLr9ESGW#teON;C@h;=a; zvXT!@k4l4*0Lp?8YZh4xVFqWy%pi&ocq9>40a>l3G;i?>HXTY5xjQ1U5D&*Ghxh;@ zqv3!)p%Kni)VduwW1^-swLxC>gM$NKBZuS3sR^$fLiH-Iu}NE5ma;Hbnq}=)J5xGk z)PQ0NWTJ+t7f*dgI~EQ$1H|%Lo&tcbhrMuBF~qBpm^Ts$I`GM|J2~xv>m>0i@8nek zpp%8zJ5h*BD5%S+@HIG3M3Vh&|I1ptJXjgn!;!mW1=erVRjC_Qk2a@9sbNsouM&po zmxANP)D{%J6-j!WfR&mOTljDQLG?(d>x5qZnpCitGRR^T`~+eco-q4any?8lP zW!D^CvtnRY#&g;Af1@jR>U9c*;a^Y!UX}(v>RJOYTG_(t8mHm9_Vo5Y^B8F6M_o+{ z^xL#%q@G*66oeer?WxZBaDnkV1-Hc?x_Agwr`*m8uj}NeKR-M_qawfhde4dM&W^k% z9%$_Jb#BjU*`Md@_~X;rk8jLueG(IbIWI=vW%q8aTHpU;&$er4M&B8^%==n@(>$8H zD6?_=&W_^z`v(Kr%b%*h>q^G(Ctno(?H3y|e)y%bG4ijwv&xAdQj;{LbsT&r!>(2gebW8KH?&c4y-R<3PufpXkropym=kN~Y^dF88 zSNDGWGj8nB%p-<|hNWW#`O&^FetE*;eEn2sq0O+hy)5S=<96&?|B$_M8SKm8u3qcx ItK8W6Z%qmByZ`_I literal 0 HcmV?d00001 diff --git a/docs/img/settings_icon.png b/docs/img/settings_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bf21c417c7817481296f471e0115634b5d7520d4 GIT binary patch literal 697 zcmV;q0!ICbP)Px%b4f%&R7gv;mRpETVHC%If29y9Ja}*kNs?#J9+cdg35m$1DYqJg7#&5%ZHSpD zifL-(lE`f|<;kNbNuJ20kRl3c4oWJSSsic)m>y3*4rMDzx?^T7z)s-0 zq$Sk?oCY=n>m+ql5<)Dc^V1?}SG52SfLXu-Np}kXJi8mfeMz$m0nBVMa1HnjjF$Ah z0Kk>@E(l^cP%r6b(*F+!X0{4g0nCjKjgn6JvzfVHDVEKDYLj%h?;DqZlBT2qdI3*&e309#R8zeNMgcQ| zvIYZ>fY*}7rTiuWFMwW2!}0(oNP3w%_y!moQlA+C9u@$6k>s3YmdHXTUZOpT^u6BR zxk=kEX0rTUbjgrnLNOnN4@i(9~MCfCrY-YASjun6H0 zGr-X>w$nUi?*lXIjt$p0?COC7QKpXf1Za}fWoFKWb13WGj0y{#}ozIG(Xk`usR+i=V@5*&E;!;tHhy z4_VMvy)Oso0lShQzhDLqPFW>o6;(BLO)XsmLt_&QOKTfjdj}T}FQ2mV>e|IimMveg za@Fd!>o;uNymRlq{RfWRxqI*agLfZ3fBE~*(rMBWph;&uT^vI+&i78b>BrHYubmjGweD_o{hz>*Dt+J-aTo_^U8bg#B)7^m^M0#*|RU3 zHs`%nh)QcVLqz-jly4gL9P9h`CLb)T@ibld^8AOVFMVEIKe~VaQwHH*%IIr;tHhy z4>j;Yd!HiE-PR>Re!&ckENmRSeEb4}Lc(J5DjGTlp5DF@F|qMUDH)mBIl1|TMWuCX zH|{ui_}GclXU<-{bou)2JFnh<`1t9o*Un3+Kx5WSqfOB)i0ljbc zs$B&x{`p_e8Rv3qcJe$UOXKSIX^r(BS(R$i=Qd|^85>5q#AF_snSSJ0XL{bG?D;dio&-dF dT3C0D`}#rklLwXF7z16-;OXk;vd$@?2>>rpGCu$S literal 0 HcmV?d00001 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/discord_rpc.hpp b/include/discord_rpc.hpp index 9b244fafc..62bd0c6b9 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 abb74089b..cf231328f 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 000000000..aaf9eaf0e --- /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 4a5238794..3cf4a1c82 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; + + using Theme = FrontendSettings::Theme; + using WindowIcon = FrontendSettings::WindowIcon; + + QTextEdit* helpText = nullptr; + QListWidget* widgetList = nullptr; + QStackedWidget* widgetContainer = nullptr; + + static constexpr size_t settingWidgetCount = 6; + std::array helpTexts; - Theme currentTheme; - QComboBox* themeSelect = nullptr; + // 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; - void setTheme(Theme theme); + 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 eb6b30e03..eb1cfb16f 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 02f2ade43..9c7de1e35 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 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/emulator.cpp b/src/emulator.cpp index fc25eacb0..1bb117b5d 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 000000000..16bae3615 --- /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 75293742e..64cade49b 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)); + + 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); - themeSelect->setGeometry(40, 40, 100, 50); - themeSelect->show(); - connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); + 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 93ce26133..fa3efae7f 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 1de9c451c..43627b666 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