From 2b59e5f46166aaed7f2b1d8917b30744cf2c7451 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 10 Jun 2026 21:27:23 +0200 Subject: [PATCH] game list in progress --- src/frontend/KaizenGui.cpp | 77 +++++++++++++-- src/frontend/KaizenGui.hpp | 8 ++ src/frontend/Settings/GeneralSettings.cpp | 47 +++++++--- src/frontend/Settings/GeneralSettings.hpp | 13 ++- src/frontend/Settings/InputSettings.cpp | 108 ++++++++++++++-------- src/utils/Options.cpp | 3 +- src/utils/Options.hpp | 5 +- 7 files changed, 195 insertions(+), 66 deletions(-) diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index 22263b1..b83536d 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,42 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User hide(); + auto romsNotFoundLabel = new QLabel(R"(Kaizen could not find any ROMs. Set the path in "Settings -> General")"); + + romsList->verticalHeader()->hide(); + romsList->setSelectionBehavior(QAbstractItemView::SelectRows); + romsList->setEditTriggers(QAbstractItemView::NoEditTriggers); + romsList->setSortingEnabled(true); + romsList->setColumnCount(3); + romsList->setHorizontalHeaderItem(0, romName); + romsList->setHorizontalHeaderItem(1, romLastPlayed); + romsList->setHorizontalHeaderItem(2, romTimePlayed); + + auto romsPath = Options::GetRomsPath(); + if (!romsPath.empty()) { + int i = 0; + centralWidget->addWidget(romsList); + for (const auto &file : fs::recursive_directory_iterator{romsPath}) { + if (file.is_regular_file() && + std::ranges::any_of(std::array{".n64", ".z64", ".v64"}, + [&](const std::string &ext) { return file.path().extension() == ext; })) { + romsList->insertRow(i); + romsList->setItem(i, 0, new QTableWidgetItem(file.path().stem().string().c_str())); + romsList->setItem(i, 1, new QTableWidgetItem("Never")); + romsList->setItem(i, 2, new QTableWidgetItem("0h 0m 0s")); + i++; + } + } + } else { + centralWidget->addWidget(romsNotFoundLabel); + } + + connect(romsList, &QTableWidget::cellDoubleClicked, this, [&](int row, int) { + auto fileToLoad = fs::path(romsPath) / romsList->item(row, 0)->text().toStdString(); + std::println("{}", fileToLoad.string()); + LoadROM(fileToLoad.string()); + }); + installEventFilter(this); restoreGeometry(settings.value("geometry").toByteArray()); @@ -23,6 +60,8 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User vulkanWidget = new RenderWidget(); vulkanWidget->hide(); + centralWidget->addWidget(vulkanWidget); + cpuTypeLabel = new QLabel("Interpreter"); if (Options::GetCpuType() == n64::CachedInterpreter) cpuTypeLabel->setText("Cached Interpreter"); @@ -39,7 +78,7 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User setWindowTitle("Kaizen " KAIZEN_VERSION_STR); setMinimumSize(640, 480); - setCentralWidget(vulkanWidget); + setCentralWidget(centralWidget); setAcceptDrops(true); @@ -69,9 +108,10 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User auto open = fileMenu->addAction("Open"); open->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_O)); connect(open, &QAction::triggered, this, [&] { - auto fileToLoad = QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", QDir::currentPath(), - "N64 ROM (*.z64 *.n64 *.v64)") - .toStdString(); + auto originPath = romsPath.empty() ? QDir::currentPath() : romsPath.c_str(); + auto fileToLoad = + QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", originPath, "N64 ROM (*.z64 *.n64 *.v64)") + .toStdString(); if (!fileToLoad.empty()) LoadROM(fileToLoad); }); @@ -83,6 +123,26 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User auto settingsMenu = emulationMenu->addAction("Settings"); settingsWindow = new SettingsWindow(); connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show); + connect(settingsWindow->general, &GeneralSettings::romFolderSelected, this, [&] { + romsPath = Options::GetRomsPath(); + if (!romsPath.empty()) { + int i = 0; + centralWidget->addWidget(romsList); + for (const auto &file : fs::recursive_directory_iterator{romsPath}) { + if (file.is_regular_file() && + std::ranges::any_of(std::array{".n64", ".z64", ".v64"}, + [&](const std::string &ext) { return file.path().extension() == ext; })) { + romsList->insertRow(i); + romsList->setItem(i, 0, new QTableWidgetItem(file.path().stem().string().c_str())); + romsList->setItem(i, 1, new QTableWidgetItem("Never")); + romsList->setItem(i, 2, new QTableWidgetItem("0h 0m 0s")); + i++; + } + } + } else { + centralWidget->addWidget(romsNotFoundLabel); + } + }); connect(settingsWindow->cpu, &CPUSettings::cpuTypeChanged, this, [&] { core.cpuType = Options::GetCpuType(); @@ -102,8 +162,7 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User auto unlockFramerate = emulationMenu->addAction("Unlock framerate"); unlockFramerate->setCheckable(true); - connect(unlockFramerate, &QAction::triggered, this, - [&](bool checked) { core.parallel.SetFramerateUnlocked(checked); }); + connect(unlockFramerate, &QAction::triggered, this, [&](bool checked) { unlockFrameratePressed = checked; }); pause->setDisabled(true); pause->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_P)); @@ -126,7 +185,7 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User emulationMenu->addAction(stop); connect(stop, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, STOP); - vulkanWidget->show(); + centralWidget->setCurrentWidget(romsList); }); auto helpMenu = menuBar()->addMenu("Help"); @@ -154,6 +213,8 @@ KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::User updateKeys(); updateAxis(); + core.parallel.SetFramerateUnlocked(unlockFrameratePressed); + auto timeStart = SDL_GetTicks(); core.Run(); core.parallel.UpdateScreen(); @@ -208,7 +269,7 @@ void KaizenGui::LoadROM(const std::string &path) noexcept { pause->setEnabled(true); reset->setEnabled(true); stop->setEnabled(true); - vulkanWidget->show(); + centralWidget->setCurrentWidget(vulkanWidget); setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + core.mem->rom.gameNameDB).c_str()); } diff --git a/src/frontend/KaizenGui.hpp b/src/frontend/KaizenGui.hpp index 022ee68..61d7acb 100644 --- a/src/frontend/KaizenGui.hpp +++ b/src/frontend/KaizenGui.hpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -30,6 +32,11 @@ class KaizenGui final : QMainWindow { bool fastForward = false; bool minimized = false; + QTableWidget *romsList = new QTableWidget(); + QTableWidgetItem *romName = new QTableWidgetItem("Name"); + QTableWidgetItem *romLastPlayed = new QTableWidgetItem("Last played"); + QTableWidgetItem *romTimePlayed = new QTableWidgetItem("Time played"); + QStackedWidget *centralWidget = new QStackedWidget(); SettingsWindow *settingsWindow; RenderWidget *vulkanWidget; QSettings settings; @@ -53,4 +60,5 @@ class KaizenGui final : QMainWindow { float elapsed = 0.f; n64::Core &core = n64::Core::GetInstance(); std::bitset<18> pressedKeys{}; + std::atomic_bool unlockFrameratePressed = false; }; diff --git a/src/frontend/Settings/GeneralSettings.cpp b/src/frontend/Settings/GeneralSettings.cpp index facfffd..ff6b538 100644 --- a/src/frontend/Settings/GeneralSettings.cpp +++ b/src/frontend/Settings/GeneralSettings.cpp @@ -5,28 +5,45 @@ #include GeneralSettings::GeneralSettings() : settings(QSettings::UserScope) { - description = new QLabel("Path:"); - description->setToolTip("Path where game saves are stored."); - selectedFolderLabel = new QLabel(Options::GetSavesPath().c_str()); - selectedFolderLabel->setDisabled(true); - folderSelectButton = new QToolButton(); + selectedRomsFolderLabel = new QLabel(Options::GetRomsPath().c_str()); + selectedRomsFolderLabel->setDisabled(true); + romsFolderSelectButton = new QToolButton(); + romsFolderSelectButton->setToolTip("Path where ROMs are stored."); - connect(folderSelectButton, &QToolButton::clicked, this, [&] { + selectedSavesFolderLabel = new QLabel(Options::GetSavesPath().c_str()); + selectedSavesFolderLabel->setDisabled(true); + savesFolderSelectButton = new QToolButton(); + savesFolderSelectButton->setToolTip("Path where game saves are stored."); + + connect(savesFolderSelectButton, &QToolButton::clicked, this, [&] { auto dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QCoreApplication::applicationDirPath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - selectedFolderLabel->setText(dir); + + selectedSavesFolderLabel->setText(dir); Options::SetSavesPath(dir.toStdString()); - settings.setValue("general/saves_path", dir); + settings.setValue("saves_path", dir); settings.sync(); }); - h = new QHBoxLayout(); + connect(romsFolderSelectButton, &QToolButton::clicked, this, [&] { + auto dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QCoreApplication::applicationDirPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - h->addWidget(description); - h->addWidget(selectedFolderLabel); - h->addWidget(folderSelectButton); + selectedRomsFolderLabel->setText(dir); + Options::SetRomsPath(dir.toStdString()); + settings.setValue("roms_path", dir); + settings.sync(); + emit romFolderSelected(); + }); - v = new QVBoxLayout(this); - v->addLayout(h); - setLayout(v); + gl = new QGridLayout(); + + gl->addWidget(new QLabel("ROMs path:"), 0, 0); + gl->addWidget(selectedRomsFolderLabel, 0, 1); + gl->addWidget(romsFolderSelectButton, 0, 2); + gl->addWidget(new QLabel("Saves path:"), 1, 0); + gl->addWidget(selectedSavesFolderLabel, 1, 1); + gl->addWidget(savesFolderSelectButton, 1, 2); + + setLayout(gl); } diff --git a/src/frontend/Settings/GeneralSettings.hpp b/src/frontend/Settings/GeneralSettings.hpp index ac08550..6d9e9bc 100644 --- a/src/frontend/Settings/GeneralSettings.hpp +++ b/src/frontend/Settings/GeneralSettings.hpp @@ -7,13 +7,16 @@ class GeneralSettings final : public QWidget { Q_OBJECT - QLabel *description; - QToolButton *folderSelectButton; - QLabel *selectedFolderLabel; - QVBoxLayout *v; - QHBoxLayout *h; + QToolButton *savesFolderSelectButton; + QLabel *selectedSavesFolderLabel; + QToolButton *romsFolderSelectButton; + QLabel *selectedRomsFolderLabel; + QGridLayout *gl; QSettings settings; public: explicit GeneralSettings(); + + signals: + void romFolderSelected(); }; diff --git a/src/frontend/Settings/InputSettings.cpp b/src/frontend/Settings/InputSettings.cpp index 223b40c..ab4bf62 100644 --- a/src/frontend/Settings/InputSettings.cpp +++ b/src/frontend/Settings/InputSettings.cpp @@ -1,24 +1,60 @@ #include InputSettings::InputSettings() : settings(QSettings::UserScope) { - mapping[0] = settings.value("input/mapping/Z", Qt::Key_Z).value(); - mapping[1] = settings.value("input/mapping/A", Qt::Key_X).value(); - mapping[2] = settings.value("input/mapping/B", Qt::Key_C).value(); - mapping[3] = settings.value("input/mapping/L", Qt::Key_A).value(); - mapping[4] = settings.value("input/mapping/R", Qt::Key_S).value(); - mapping[5] = settings.value("input/mapping/S", Qt::Key_Return).value(); - mapping[6] = settings.value("input/mapping/DU", Qt::Key_I).value(); - mapping[7] = settings.value("input/mapping/DD", Qt::Key_K).value(); - mapping[8] = settings.value("input/mapping/DL", Qt::Key_J).value(); - mapping[9] = settings.value("input/mapping/DR", Qt::Key_L).value(); - mapping[10] = settings.value("input/mapping/CU", Qt::Key_T).value(); - mapping[11] = settings.value("input/mapping/CD", Qt::Key_G).value(); - mapping[12] = settings.value("input/mapping/CL", Qt::Key_F).value(); - mapping[13] = settings.value("input/mapping/CR", Qt::Key_H).value(); - mapping[14] = settings.value("input/mapping/AU", Qt::Key_Up).value(); - mapping[15] = settings.value("input/mapping/AD", Qt::Key_Down).value(); - mapping[16] = settings.value("input/mapping/AL", Qt::Key_Left).value(); - mapping[17] = settings.value("input/mapping/AR", Qt::Key_Right).value(); + auto keyZ = settings.value("input/Z", Qt::Key_Z).value(); + auto keyA = settings.value("input/A", Qt::Key_X).value(); + auto keyB = settings.value("input/B", Qt::Key_C).value(); + auto keyL = settings.value("input/L", Qt::Key_A).value(); + auto keyR = settings.value("input/R", Qt::Key_S).value(); + auto keyS = settings.value("input/S", Qt::Key_Return).value(); + auto keyDU = settings.value("input/DU", Qt::Key_I).value(); + auto keyDD = settings.value("input/DD", Qt::Key_K).value(); + auto keyDL = settings.value("input/DL", Qt::Key_J).value(); + auto keyDR = settings.value("input/DR", Qt::Key_L).value(); + auto keyCU = settings.value("input/CU", Qt::Key_T).value(); + auto keyCD = settings.value("input/CD", Qt::Key_G).value(); + auto keyCL = settings.value("input/CL", Qt::Key_F).value(); + auto keyCR = settings.value("input/CR", Qt::Key_H).value(); + auto keyAU = settings.value("input/AU", Qt::Key_Up).value(); + auto keyAD = settings.value("input/AD", Qt::Key_Down).value(); + auto keyAL = settings.value("input/AL", Qt::Key_Left).value(); + auto keyAR = settings.value("input/AR", Qt::Key_Right).value(); + mapping[0] = keyZ; + mapping[1] = keyA; + mapping[2] = keyB; + mapping[3] = keyL; + mapping[4] = keyR; + mapping[5] = keyS; + mapping[6] = keyDU; + mapping[7] = keyDD; + mapping[8] = keyDL; + mapping[9] = keyDR; + mapping[10] = keyCU; + mapping[11] = keyCD; + mapping[12] = keyCL; + mapping[13] = keyCR; + mapping[14] = keyAU; + mapping[15] = keyAD; + mapping[16] = keyAL; + mapping[17] = keyAR; + btnZ->setKeySequence(keyZ); + btnA->setKeySequence(keyA); + btnB->setKeySequence(keyB); + btnL->setKeySequence(keyL); + btnR->setKeySequence(keyR); + btnS->setKeySequence(keyS); + btnDU->setKeySequence(keyDU); + btnDD->setKeySequence(keyDD); + btnDL->setKeySequence(keyDL); + btnDR->setKeySequence(keyDR); + btnCU->setKeySequence(keyCU); + btnCD->setKeySequence(keyCD); + btnCL->setKeySequence(keyCL); + btnCR->setKeySequence(keyCR); + btnAU->setKeySequence(keyAU); + btnAD->setKeySequence(keyAD); + btnAL->setKeySequence(keyAL); + btnAR->setKeySequence(keyAR); form = new QFormLayout(); @@ -43,92 +79,92 @@ InputSettings::InputSettings() : settings(QSettings::UserScope) { connect(btnZ, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[0] = btnZ->keySequence(); - settings.setValue("input/mapping/Z", mapping[0]); + settings.setValue("input/Z", mapping[0]); settings.sync(); }); connect(btnA, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[1] = btnA->keySequence(); - settings.setValue("input/mapping/A", mapping[1]); + settings.setValue("input/A", mapping[1]); settings.sync(); }); connect(btnB, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[2] = btnB->keySequence(); - settings.setValue("input/mapping/B", mapping[2]); + settings.setValue("input/B", mapping[2]); settings.sync(); }); connect(btnL, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[3] = btnL->keySequence(); - settings.setValue("input/mapping/L", mapping[3]); + settings.setValue("input/L", mapping[3]); settings.sync(); }); connect(btnR, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[4] = btnR->keySequence(); - settings.setValue("input/mapping/R", mapping[4]); + settings.setValue("input/R", mapping[4]); settings.sync(); }); connect(btnS, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[5] = btnS->keySequence(); - settings.setValue("input/mapping/S", mapping[5]); + settings.setValue("input/S", mapping[5]); settings.sync(); }); connect(btnDU, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[6] = btnDU->keySequence(); - settings.setValue("input/mapping/DU", mapping[6]); + settings.setValue("input/DU", mapping[6]); settings.sync(); }); connect(btnDD, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[7] = btnDD->keySequence(); - settings.setValue("input/mapping/DD", mapping[7]); + settings.setValue("input/DD", mapping[7]); settings.sync(); }); connect(btnDL, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[8] = btnDL->keySequence(); - settings.setValue("input/mapping/DL", mapping[8]); + settings.setValue("input/DL", mapping[8]); settings.sync(); }); connect(btnDR, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[9] = btnDR->keySequence(); - settings.setValue("input/mapping/DR", mapping[9]); + settings.setValue("input/DR", mapping[9]); settings.sync(); }); connect(btnCU, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[10] = btnCU->keySequence(); - settings.setValue("input/mapping/CU", mapping[10]); + settings.setValue("input/CU", mapping[10]); settings.sync(); }); connect(btnCD, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[11] = btnCD->keySequence(); - settings.setValue("input/mapping/CD", mapping[11]); + settings.setValue("input/CD", mapping[11]); settings.sync(); }); connect(btnCL, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[12] = btnCL->keySequence(); - settings.setValue("input/mapping/CL", mapping[12]); + settings.setValue("input/CL", mapping[12]); settings.sync(); }); connect(btnCR, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[13] = btnCR->keySequence(); - settings.setValue("input/mapping/CR", mapping[13]); + settings.setValue("input/CR", mapping[13]); settings.sync(); }); connect(btnAU, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[14] = btnAU->keySequence(); - settings.setValue("input/mapping/AU", mapping[14]); + settings.setValue("input/AU", mapping[14]); settings.sync(); }); connect(btnAD, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[15] = btnAD->keySequence(); - settings.setValue("input/mapping/AD", mapping[15]); + settings.setValue("input/AD", mapping[15]); settings.sync(); }); connect(btnAL, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[16] = btnAL->keySequence(); - settings.setValue("input/mapping/AL", mapping[16]); + settings.setValue("input/AL", mapping[16]); settings.sync(); }); connect(btnAR, &QKeySequenceEdit::keySequenceChanged, this, [&] { mapping[17] = btnAR->keySequence(); - settings.setValue("input/mapping/AR", mapping[17]); + settings.setValue("input/AR", mapping[17]); settings.sync(); }); diff --git a/src/utils/Options.cpp b/src/utils/Options.cpp index 555da60..8e89d79 100644 --- a/src/utils/Options.cpp +++ b/src/utils/Options.cpp @@ -6,5 +6,6 @@ Options::Options() { volume = settings.value("audio/volume", 0.5f).toFloat(); cpuType = settings.value("cpu/type", n64::PlainInterpreter).value(); idleSkip = settings.value("cpu/idle_skip", false).toBool(); - savesPath = settings.value("general/saves_path", "saves").toString().toStdString(); + savesPath = settings.value("saves_path", "").toString().toStdString(); + romsPath = settings.value("roms_path", "").toString().toStdString(); } diff --git a/src/utils/Options.hpp b/src/utils/Options.hpp index 274d432..6ec3db6 100644 --- a/src/utils/Options.hpp +++ b/src/utils/Options.hpp @@ -18,14 +18,17 @@ struct Options { static float GetVolume() { return GetInstance().volume; } static n64::CPUType GetCpuType() { return GetInstance().cpuType; } static std::string GetSavesPath() { return GetInstance().savesPath; } + static std::string GetRomsPath() { return GetInstance().romsPath; } static void SetIdleSkip(bool v) { GetInstance().idleSkip = v; } static void SetVolume(float v) { GetInstance().volume = v; } static void SetCpuType(n64::CPUType v) { GetInstance().cpuType = v; } static void SetSavesPath(const std::string &v) { GetInstance().savesPath = v; } + static void SetRomsPath(const std::string &v) { GetInstance().romsPath = v; } private: bool idleSkip = false; float volume = 0.5; - std::string savesPath = "saves"; + std::string savesPath; + std::string romsPath; n64::CPUType cpuType = n64::PlainInterpreter; };