From 8d5485fbeb60023e9c0c8823d3603ee032cd3103 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:20:47 +0200 Subject: [PATCH 1/6] [Qt] Add proper message queue for thread communication --- include/panda_qt/main_window.hpp | 31 ++++++++++++++++--- src/panda_qt/main_window.cpp | 51 +++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index b5b93d56..0187b424 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -9,9 +9,11 @@ #include #include #include +#include #include "emulator.hpp" #include "panda_qt/screen.hpp" +#include "services/hid.hpp" class MainWindow : public QMainWindow { Q_OBJECT @@ -23,15 +25,34 @@ class MainWindow : public QMainWindow { Dark = 2, }; + // Types of messages we might send from the GUI thread to the emulator thread + enum class MessageType { + LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey + }; + + // Tagged union representing our message queue messages + struct EmulatorMessage { + MessageType type; + + union { + struct { + std::filesystem::path* p; + } path; + + struct { + u32 key; + } key; + }; + }; + // This would normally be an std::unique_ptr but it's shared between threads so definitely not Emulator* emu = nullptr; std::thread emuThread; std::atomic appRunning = true; // Is the application itself running? - std::mutex messageQueueMutex; // Used for synchronizing messages between the emulator and UI - std::filesystem::path romToLoad = ""; - - bool needToLoadROM = false; + // Used for synchronizing messages between the emulator and UI + std::mutex messageQueueMutex; + std::vector messageQueue; ScreenWidget screen; QComboBox* themeSelect = nullptr; @@ -43,6 +64,8 @@ class MainWindow : public QMainWindow { void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void sendMessage(const EmulatorMessage& message); + void dispatchMessage(const EmulatorMessage& message); // Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer bool usingGL = false; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 2c2cc64f..8ffaf7b6 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -73,13 +73,13 @@ void MainWindow::emuThreadMainLoop() { { std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - needToLoadROM = false; - - bool success = emu->loadROM(romToLoad); - if (!success) { - printf("Failed to load ROM"); + // Dispatch all messages in the message queue + if (!messageQueue.empty()) { + for (const auto& msg : messageQueue) { + dispatchMessage(msg); } + + messageQueue.clear(); } } @@ -102,23 +102,15 @@ void MainWindow::swapEmuBuffer() { } void MainWindow::selectROM() { - // Are we already waiting for a ROM to be loaded? Then complain about it! - { - std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - QMessageBox::warning(this, tr("Already loading ROM"), tr("Panda3DS is already busy loading a ROM, please wait")); - return; - } - } - auto path = QFileDialog::getOpenFileName(this, tr("Select 3DS ROM to load"), "", tr("Nintendo 3DS ROMs (*.3ds *.cci *.cxi *.app *.3dsx *.elf *.axf)")); if (!path.isEmpty()) { - std::unique_lock lock(messageQueueMutex); + std::filesystem::path* p = new std::filesystem::path(path.toStdU16String()); - romToLoad = path.toStdU16String(); - needToLoadROM = true; + EmulatorMessage message{.type = MessageType::LoadROM}; + message.path.p = p; + sendMessage(message); } } @@ -135,6 +127,12 @@ MainWindow::~MainWindow() { delete themeSelect; } +// Send a message to the emulator thread. Lock the mutex and just push back to the vector. +void MainWindow::sendMessage(const EmulatorMessage& message) { + std::unique_lock lock(messageQueueMutex); + messageQueue.push_back(message); +} + void MainWindow::setTheme(Theme theme) { currentTheme = theme; @@ -225,4 +223,21 @@ void MainWindow::dumpRomFS() { QMessageBox::warning(this, tr("No RomFS found"), tr("No RomFS partition was found in the loaded app")); break; } +} + +void MainWindow::dispatchMessage(const EmulatorMessage& message) { + switch (message.type) { + case MessageType::LoadROM: + emu->loadROM(*message.path.p); + // Clean up the allocated path + delete message.path.p; + break; + + case MessageType::Pause: emu->pause(); break; + case MessageType::Resume: emu->resume(); break; + case MessageType::TogglePause: emu->togglePause(); break; + case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break; + case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; + case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; + } } \ No newline at end of file From a36fd6dd57bc45778290027af7a9eff0d9c1cb8f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:41:09 +0200 Subject: [PATCH 2/6] [Qt] Add basic input --- include/panda_qt/main_window.hpp | 3 ++ src/panda_qt/main_window.cpp | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 0187b424..f9eb4f63 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -74,4 +74,7 @@ class MainWindow : public QMainWindow { public: MainWindow(QApplication* app, QWidget* parent = nullptr); ~MainWindow(); + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; }; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 8ffaf7b6..8b70c04c 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -240,4 +240,60 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; } +} + +void MainWindow::keyPressEvent(QKeyEvent* event) { + auto pressKey = [this](u32 key) { + EmulatorMessage message{.type = MessageType::PressKey}; + message.key.key = key; + + sendMessage(message); + }; + + switch (event->key()) { + case Qt::Key_L: pressKey(HID::Keys::A); break; + case Qt::Key_K: pressKey(HID::Keys::B); break; + case Qt::Key_O: pressKey(HID::Keys::X); break; + case Qt::Key_I: pressKey(HID::Keys::Y); break; + + case Qt::Key_Q: pressKey(HID::Keys::L); break; + case Qt::Key_P: pressKey(HID::Keys::R); break; + + case Qt::Key_Right: pressKey(HID::Keys::Right); break; + case Qt::Key_Left: pressKey(HID::Keys::Left); break; + case Qt::Key_Up: pressKey(HID::Keys::Up); break; + case Qt::Key_Down: pressKey(HID::Keys::Down); break; + + case Qt::Key_Return: pressKey(HID::Keys::Start); break; + case Qt::Key_Backspace: pressKey(HID::Keys::Select); break; + case Qt::Key_F4: sendMessage(EmulatorMessage{.type = MessageType::TogglePause}); break; + case Qt::Key_F5: sendMessage(EmulatorMessage{.type = MessageType::Reset}); break; + } +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) { + auto releaseKey = [this](u32 key) { + EmulatorMessage message{.type = MessageType::ReleaseKey}; + message.key.key = key; + + sendMessage(message); + }; + + switch (event->key()) { + case Qt::Key_L: releaseKey(HID::Keys::A); break; + case Qt::Key_K: releaseKey(HID::Keys::B); break; + case Qt::Key_O: releaseKey(HID::Keys::X); break; + case Qt::Key_I: releaseKey(HID::Keys::Y); break; + + case Qt::Key_Q: releaseKey(HID::Keys::L); break; + case Qt::Key_P: releaseKey(HID::Keys::R); break; + + case Qt::Key_Right: releaseKey(HID::Keys::Right); break; + case Qt::Key_Left: releaseKey(HID::Keys::Left); break; + case Qt::Key_Up: releaseKey(HID::Keys::Up); break; + case Qt::Key_Down: releaseKey(HID::Keys::Down); break; + + case Qt::Key_Return: releaseKey(HID::Keys::Start); break; + case Qt::Key_Backspace: releaseKey(HID::Keys::Select); break; + } } \ No newline at end of file From 9bbaab78712feceb161ae2319aad448b89aad72b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:46:11 +0200 Subject: [PATCH 3/6] [Qt] Add Pause/Resume/Reset --- src/panda_qt/main_window.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 8b70c04c..ab717087 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -27,6 +27,13 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto pandaAction = fileMenu->addAction(tr("panda...")); connect(pandaAction, &QAction::triggered, this, &MainWindow::selectROM); + auto pauseAction = emulationMenu->addAction(tr("Pause")); + auto resumeAction = emulationMenu->addAction(tr("Resume")); + auto resetAction = emulationMenu->addAction(tr("Reset")); + connect(pauseAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Pause}); }); + connect(resumeAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Resume}); }); + connect(resetAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Reset}); }); + auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); From b0261234a6a541fed677b2a8a995ce39bd2d5567 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:53:41 +0200 Subject: [PATCH 4/6] [Qt] Properly update HID service --- include/emulator.hpp | 2 ++ src/panda_qt/main_window.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/emulator.hpp b/include/emulator.hpp index df930373..2da76847 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -52,12 +52,14 @@ class Emulator { // We bind gyro to right click + mouse movement bool holdingRightClick = false; + public: static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; bool running = false; // Is the emulator running a game? bool programRunning = false; // Is the emulator program itself running? + private: #ifdef PANDA3DS_ENABLE_HTTP_SERVER HttpServer httpServer; friend struct HttpServer; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index ab717087..adfd3470 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -91,6 +91,10 @@ void MainWindow::emuThreadMainLoop() { } emu->runFrame(); + if (emu->romType != ROMType::None) { + emu->getServiceManager().getHID().updateInputs(emu->getTicks()); + } + swapEmuBuffer(); } From 7571e58ce5aa7043de0e1a5ee4a2baa30f935301 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:14:03 +0200 Subject: [PATCH 5/6] [Qt] Add pink theme --- include/panda_qt/main_window.hpp | 1 + src/panda_qt/main_window.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index f9eb4f63..31fb1e0d 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -23,6 +23,7 @@ class MainWindow : public QMainWindow { System = 0, Light = 1, Dark = 2, + GreetingsCat = 3, }; // Types of messages we might send from the GUI thread to the emulator thread diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index adfd3470..33e27a0d 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -43,6 +43,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) themeSelect->addItem(tr("System")); themeSelect->addItem(tr("Light")); themeSelect->addItem(tr("Dark")); + themeSelect->addItem(tr("Greetings Cat")); themeSelect->setCurrentIndex(static_cast(currentTheme)); themeSelect->setGeometry(40, 40, 100, 50); @@ -192,6 +193,29 @@ void MainWindow::setTheme(Theme theme) { break; } + case Theme::GreetingsCat: { + QApplication::setStyle(QStyleFactory::create("Fusion")); + + QPalette p; + p.setColor(QPalette::Window, QColor(250, 207, 228)); + p.setColor(QPalette::WindowText, QColor(225, 22, 137)); + p.setColor(QPalette::Base, QColor(250, 207, 228)); + p.setColor(QPalette::AlternateBase, QColor(250, 207, 228)); + p.setColor(QPalette::ToolTipBase, QColor(225, 22, 137)); + p.setColor(QPalette::ToolTipText, QColor(225, 22, 137)); + p.setColor(QPalette::Text, QColor(225, 22, 137)); + p.setColor(QPalette::Button, QColor(250, 207, 228)); + p.setColor(QPalette::ButtonText, QColor(225, 22, 137)); + p.setColor(QPalette::BrightText, Qt::black); + p.setColor(QPalette::Link, QColor(42, 130, 218)); + + p.setColor(QPalette::Highlight, QColor(42, 130, 218)); + p.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(p); + break; + } + + case Theme::System: { qApp->setPalette(this->style()->standardPalette()); qApp->setStyle(QStyleFactory::create("WindowsVista")); From 1769783dc7e10358c5c34650f34375c9295a674f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:49:32 +0200 Subject: [PATCH 6/6] Add about menu --- CMakeLists.txt | 6 +-- docs/img/rstarstruck_icon.png | Bin 0 -> 9132 bytes include/panda_qt/about_window.hpp | 12 ++++++ include/panda_qt/main_window.hpp | 3 ++ src/panda_qt/about_window.cpp | 62 ++++++++++++++++++++++++++++++ src/panda_qt/main_window.cpp | 14 ++++++- 6 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 docs/img/rstarstruck_icon.png create mode 100644 include/panda_qt/about_window.hpp create mode 100644 src/panda_qt/about_window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a718fbb..c202f80c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,8 +185,8 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files if(ENABLE_QT_GUI) - set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp) - set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp) + set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) @@ -429,7 +429,7 @@ if(ENABLE_QT_GUI) qt_add_resources(Alber "app_images" PREFIX "/" FILES - docs/img/rsob_icon.png + docs/img/rsob_icon.png docs/img/rstarstruck_icon.png ) else() target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_SDL=1") diff --git a/docs/img/rstarstruck_icon.png b/docs/img/rstarstruck_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2b4aab062193120af369718fe3810c690e14ad83 GIT binary patch literal 9132 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 +#include +#include + +class AboutWindow : public QDialog { + Q_OBJECT + + public: + AboutWindow(QWidget* parent = nullptr); +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 31fb1e0d..a50ee9a1 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -12,6 +12,7 @@ #include #include "emulator.hpp" +#include "panda_qt/about_window.hpp" #include "panda_qt/screen.hpp" #include "services/hid.hpp" @@ -56,6 +57,7 @@ class MainWindow : public QMainWindow { std::vector messageQueue; ScreenWidget screen; + AboutWindow* aboutWindow; QComboBox* themeSelect = nullptr; QMenuBar* menuBar = nullptr; @@ -65,6 +67,7 @@ class MainWindow : public QMainWindow { void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void showAboutMenu(); void sendMessage(const EmulatorMessage& message); void dispatchMessage(const EmulatorMessage& message); diff --git a/src/panda_qt/about_window.cpp b/src/panda_qt/about_window.cpp new file mode 100644 index 00000000..6f799c23 --- /dev/null +++ b/src/panda_qt/about_window.cpp @@ -0,0 +1,62 @@ +#include "panda_qt/about_window.hpp" + +#include +#include +#include +#include + +// Based on https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DolphinQt/AboutDialog.cpp + +AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { + resize(200, 200); + + setWindowTitle(tr("About Panda3DS")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + const QString text = + QStringLiteral(R"( +

Panda3DS

+ +

+%ABOUT_PANDA3DS%
+
%SUPPORT%
+

+ +

+%AUTHORS% +

+)") + .replace(QStringLiteral("%ABOUT_PANDA3DS%"), tr("Panda3DS is a free and open source Nintendo 3DS emulator, for Windows, MacOS and Linux")) + .replace(QStringLiteral("%SUPPORT%"), tr("Visit panda3ds.com for help with Panda3DS and links to our official support sites.")) + .replace( + QStringLiteral("%AUTHORS%"), tr("Panda3DS is developed by volunteers in their spare time. Below is a list of some of these" + " volunteers who've agreed to be listed here, in no particular order.
If you think you should be " + "listed here too, please inform us

" + "- Peach (wheremyfoodat)
" + "- noumidev
" + "- liuk707
" + "- Wunk
" + "- marysaka
" + "- Sky
" + "- merryhime
" + "- TGP17
") + ); + + QLabel* textLabel = new QLabel(text); + textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + textLabel->setOpenExternalLinks(true); + + QLabel* logo = new QLabel(); + logo->setPixmap(QPixmap(":/docs/img/rstarstruck_icon.png")); + logo->setContentsMargins(30, 0, 30, 0); + + QVBoxLayout* mainLayout = new QVBoxLayout; + QHBoxLayout* hLayout = new QHBoxLayout; + + setLayout(mainLayout); + mainLayout->addLayout(hLayout); + + hLayout->setAlignment(Qt::AlignLeft); + hLayout->addWidget(logo); + hLayout->addWidget(textLabel); +} \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 33e27a0d..804dc63a 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -20,7 +20,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto fileMenu = menuBar->addMenu(tr("File")); auto emulationMenu = menuBar->addMenu(tr("Emulation")); auto toolsMenu = menuBar->addMenu(tr("Tools")); - auto helpMenu = menuBar->addMenu(tr("Help")); auto aboutMenu = menuBar->addMenu(tr("About")); // Create and bind actions for them @@ -37,6 +36,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); + auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); + connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); + // Set up theme selection setTheme(Theme::Dark); themeSelect = new QComboBox(this); @@ -50,6 +52,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) themeSelect->show(); connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); + // Set up misc objects + aboutWindow = new AboutWindow(nullptr); + emu = new Emulator(); emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); @@ -136,6 +141,7 @@ MainWindow::~MainWindow() { delete emu; delete menuBar; + delete aboutWindow; delete themeSelect; } @@ -215,7 +221,6 @@ void MainWindow::setTheme(Theme theme) { break; } - case Theme::System: { qApp->setPalette(this->style()->standardPalette()); qApp->setStyle(QStyleFactory::create("WindowsVista")); @@ -260,6 +265,11 @@ void MainWindow::dumpRomFS() { } } +void MainWindow::showAboutMenu() { + AboutWindow about(this); + about.exec(); +} + void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { case MessageType::LoadROM: