Files
kaizen/src/frontend/KaizenGui.cpp
T

422 lines
16 KiB
C++

#include <KaizenGui.hpp>
#include <QMenuBar>
#include <QMenu>
#include <QMessageBox>
#include <QCoreApplication>
#include <QStatusBar>
#include <QTimer>
#include <QMimeData>
#include <QHeaderView>
#include <resources/gamecontrollerdb.h>
#include <Options.hpp>
#include <Scheduler.hpp>
void KaizenGui::populateRomsList(const std::string &romsPath) {
centralWidget->setCurrentWidget(romPathNotSet);
if (!romsPath.empty()) {
int i = 0;
centralWidget->setCurrentWidget(romsList);
for (const auto &file : fs::recursive_directory_iterator{romsPath}) {
if (!file.is_regular_file())
continue;
auto filename = file.path().string();
bool isPlain = std::ranges::any_of(std::array{".n64", ".z64", ".v64"},
[&](const std::string &ext) { return file.path().extension() == ext; });
bool isArchive =
std::ranges::any_of(std::array{".zip", ".7z", ".rar", ".tar"},
[&](const std::string &ext) { return file.path().extension() == ext; });
if (!isArchive && !isPlain)
continue;
auto rom = n64::Mem::LoadROM(isArchive, filename);
auto regions = n64::GameDB::match(rom);
if (rom.gameNameDB.empty())
rom.gameNameDB = fs::path(filename).stem();
romsListPaths.push_back(filename);
romsList->insertRow(i);
romsList->setItem(
i, 0,
new QTableWidgetItem(std::format("{} ({}) (Rev {})", rom.gameNameDB,
n64::GameDB::regionCodeToReadable(rom.header.countryCode),
rom.header.version)
.c_str()));
romsList->setItem(i, 1, new QTableWidgetItem(regions.c_str()));
romsList->setItem(i, 2, new QTableWidgetItem("Never"));
romsList->setItem(i, 3, new QTableWidgetItem("0h 0m 0s"));
i++;
}
}
currentHomeWidget = centralWidget->currentWidget();
}
KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::UserScope) {
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
SDL_AddGamepadMapping(gamecontrollerdb_str);
hide();
romsList->verticalHeader()->hide();
romsList->setSizePolicy({QSizePolicy::Maximum, QSizePolicy::Maximum});
romsList->setSelectionMode(QAbstractItemView::SingleSelection);
romsList->setSelectionBehavior(QAbstractItemView::SelectRows);
romsList->setEditTriggers(QAbstractItemView::NoEditTriggers);
romsList->setSortingEnabled(true);
romsList->setColumnCount(4);
romsList->setHorizontalHeaderItem(0, new QTableWidgetItem("Name"));
romsList->setHorizontalHeaderItem(1, new QTableWidgetItem("Regions"));
romsList->setHorizontalHeaderItem(2, new QTableWidgetItem("Last played"));
romsList->setHorizontalHeaderItem(3, new QTableWidgetItem("Time played"));
vulkanWidget = new RenderWidget();
vulkanWidget->hide();
centralWidget->addWidget(vulkanWidget);
centralWidget->addWidget(romsList);
centralWidget->addWidget(romPathNotSet);
populateRomsList(Options::GetRomsPath());
connect(romsList, &QTableWidget::cellDoubleClicked, this, [&](int row, int) {
auto fileToLoad = fs::path(Options::GetRomsPath()) / romsListPaths[row];
std::println("{}", fileToLoad.string());
LoadROM(fileToLoad.string());
});
installEventFilter(this);
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
cpuTypeLabel = new QLabel("Interpreter");
if (Options::GetCpuType() == n64::CachedInterpreter)
cpuTypeLabel->setText("Cached Interpreter");
idleSkipLabel = new QLabel("Idle skipping");
if (!Options::GetIdleSkip() || Options::GetCpuType() == n64::PlainInterpreter)
idleSkipLabel->hide();
fpsLabel = new QLabel("Not running");
statusBar()->addWidget(fpsLabel);
statusBar()->addWidget(cpuTypeLabel);
statusBar()->addWidget(idleSkipLabel);
setWindowTitle("Kaizen " KAIZEN_VERSION_STR);
setMinimumSize(640, 480);
setCentralWidget(centralWidget);
setAcceptDrops(true);
SDLeventsTimer = new QTimer();
SDLeventsTimer->setInterval(1);
statusBarTimer = new QTimer();
statusBarTimer->setInterval(500);
connect(SDLeventsTimer, &QTimer::timeout, this, [&] {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
if (!gamepad) {
const auto index = e.gdevice.which;
gamepad = SDL_OpenGamepad(index);
warn("Found controller!");
warn("Name: {}", SDL_GetGamepadName(gamepad));
warn("Vendor: {}", SDL_GetGamepadVendor(gamepad));
}
}
if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
if (gamepad) {
SDL_CloseGamepad(gamepad);
gamepad = nullptr;
}
}
}
});
SDLeventsTimer->start();
connect(statusBarTimer, &QTimer::timeout, this, [&] {
pause->setText("Pause");
fpsLabel->setText(std::format("FPS: {:.2f}", 1000.f / elapsed).c_str());
if (core.pause) {
pause->setText("Resume");
fpsLabel->setText("Paused");
}
if (!core.romLoaded) {
pause->setText("Pause");
pause->setDisabled(true);
reset->setDisabled(true);
stop->setDisabled(true);
fpsLabel->setText("Not running");
}
});
statusBarTimer->start();
auto fileMenu = menuBar()->addMenu("File");
auto open = fileMenu->addAction("Open");
open->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_O));
connect(open, &QAction::triggered, this, [&] {
auto originPath = Options::GetRomsPath().empty() ? QDir::currentPath() : Options::GetRomsPath().c_str();
auto fileToLoad =
QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", originPath, "N64 ROM (*.z64 *.n64 *.v64)")
.toStdString();
if (!fileToLoad.empty())
LoadROM(fileToLoad);
});
auto exit = fileMenu->addAction("Exit");
connect(exit, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, STOP); });
connect(exit, &QAction::triggered, this, &QMainWindow::close);
auto emulationMenu = menuBar()->addMenu("Emulation");
auto settingsMenu = emulationMenu->addAction("Settings");
settingsWindow = new SettingsWindow();
connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show);
connect(settingsWindow->general, &GeneralSettings::romFolderSelected, this,
[&] { populateRomsList(Options::GetRomsPath()); });
connect(settingsWindow->cpu, &CPUSettings::cpuTypeChanged, this, [&] {
core.cpuType = Options::GetCpuType();
cpuTypeLabel->setText("Cached Interpreter");
idleSkipLabel->setVisible(Options::GetCpuType() == n64::CachedInterpreter);
if (Options::GetCpuType() == n64::PlainInterpreter)
cpuTypeLabel->setText("Interpreter");
});
connect(settingsWindow->cpu, &CPUSettings::idleSkipChanged, this, [&] {
core.idleSkip = Options::GetIdleSkip();
idleSkipLabel->setVisible(Options::GetIdleSkip());
});
emulationMenu->addSeparator();
auto unlockFramerate = emulationMenu->addAction("Unlock framerate");
unlockFramerate->setCheckable(true);
connect(unlockFramerate, &QAction::triggered, this, [&](bool checked) { unlockFrameratePressed = checked; });
pause->setDisabled(true);
pause->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_P));
emulationMenu->addAction(pause);
connect(pause, &QAction::triggered, this, [&] {
if (!core.pause)
Scheduler::GetInstance().EnqueueRelative(0, PAUSE);
else
core.TogglePause();
});
reset->setDisabled(true);
reset->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_R));
emulationMenu->addAction(reset);
connect(reset, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, RESET); });
stop->setDisabled(true);
stop->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_X));
emulationMenu->addAction(stop);
connect(stop, &QAction::triggered, this, [&] {
Scheduler::GetInstance().EnqueueRelative(0, STOP);
centralWidget->setCurrentWidget(currentHomeWidget);
});
auto helpMenu = menuBar()->addMenu("Help");
auto about = helpMenu->addAction("About");
connect(about, &QAction::triggered, this, [&] {
auto text = std::format("<p>Kaizen is a Nintendo 64 emulator that strives<br>"
"to offer a friendly user experience and compatibility.<br>"
"Kaizen is licensed under the BSD 3-clause license.<br>"
"Nintendo 64 is a registered trademark of Nintendo Co., Ltd.</p><hr>"
"Kaizen {}{}",
KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
QMessageBox::about(this, "About", text.c_str());
});
show();
emuThread = QThread::create([&] {
core.parallel.Init(vulkanWidget->wsiPlatform, vulkanWidget->windowInfo, vulkanWidget->qtVkInstanceFactory.get(),
core.GetMem().GetRDRAMPtr());
while (!emuThread->isInterruptionRequested()) {
if (!core.romLoaded || core.pause)
continue;
updateKeys();
updateAxis();
core.parallel.SetFramerateUnlocked(unlockFrameratePressed);
auto timeStart = SDL_GetTicks();
core.Run();
core.parallel.UpdateScreen();
elapsed = SDL_GetTicks() - timeStart;
}
});
emuThread->start();
}
KaizenGui::~KaizenGui() { cleanup(); }
void KaizenGui::updateKeys() {
auto &pif = core.mem->mmio.si.pif;
if (gamepad) {
pif.UpdateButton(0, n64::Controller::Z,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::CUp,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) == SDL_JOYSTICK_AXIS_MIN);
pif.UpdateButton(0, n64::Controller::CDown,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::CLeft,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) == SDL_JOYSTICK_AXIS_MIN);
pif.UpdateButton(0, n64::Controller::CRight,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) == SDL_JOYSTICK_AXIS_MAX);
return;
}
pif.UpdateButton(0, n64::Controller::Z, pressedKeys.test(0));
pif.UpdateButton(0, n64::Controller::A, pressedKeys.test(1));
pif.UpdateButton(0, n64::Controller::B, pressedKeys.test(2));
pif.UpdateButton(0, n64::Controller::LT, pressedKeys.test(3));
pif.UpdateButton(0, n64::Controller::RT, pressedKeys.test(4));
pif.UpdateButton(0, n64::Controller::Start, pressedKeys.test(5));
pif.UpdateButton(0, n64::Controller::DUp, pressedKeys.test(6));
pif.UpdateButton(0, n64::Controller::DDown, pressedKeys.test(7));
pif.UpdateButton(0, n64::Controller::DLeft, pressedKeys.test(8));
pif.UpdateButton(0, n64::Controller::DRight, pressedKeys.test(9));
pif.UpdateButton(0, n64::Controller::CUp, pressedKeys.test(10));
pif.UpdateButton(0, n64::Controller::CDown, pressedKeys.test(11));
pif.UpdateButton(0, n64::Controller::CLeft, pressedKeys.test(12));
pif.UpdateButton(0, n64::Controller::CRight, pressedKeys.test(13));
}
void KaizenGui::updateAxis() {
auto &pif = core.mem->mmio.si.pif;
if (gamepad) {
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast<s8>(-yclamped));
pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast<s8>(xclamped));
return;
}
s16 x = 0, y = 0;
if (pressedKeys.test(14)) // up
y += 86;
if (pressedKeys.test(15)) // down
y -= 86;
if (pressedKeys.test(16)) // left
x -= 86;
if (pressedKeys.test(17)) // right
x += 86;
core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::X, x);
core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::Y, y);
}
void KaizenGui::LoadROM(const std::string &path) noexcept {
core.LoadROM(path);
pause->setEnabled(true);
reset->setEnabled(true);
stop->setEnabled(true);
centralWidget->setCurrentWidget(vulkanWidget);
setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + core.mem->rom.gameNameDB).c_str());
}
void KaizenGui::LoadTAS(const std::string &path) noexcept { core.LoadTAS(fs::path(path)); }
void KaizenGui::cleanup() {
SDL_Quit();
core.Stop();
emuThread->requestInterruption();
emuThread->quit();
delete emuThread;
delete vulkanWidget;
delete settingsWindow;
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
}
void KaizenGui::dropEvent(QDropEvent *event) {
if (event->mimeData()->hasUrls()) {
auto file = event->mimeData()->urls()[0].toLocalFile().toStdString();
LoadROM(file);
event->acceptProposedAction();
}
}
void KaizenGui::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasUrls())
event->acceptProposedAction();
}
void KaizenGui::keyPressEvent(QKeyEvent *event) {
for (int i = 0; i < settingsWindow->input->mapping.size(); i++) {
if (settingsWindow->input->mapping[i] == event->key())
pressedKeys.set(i);
}
}
void KaizenGui::keyReleaseEvent(QKeyEvent *event) {
for (int i = 0; i < settingsWindow->input->mapping.size(); i++) {
if (settingsWindow->input->mapping[i] == event->key())
pressedKeys.reset(i);
}
}
bool KaizenGui::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (!settingsWindow->hasFocus())
return keyPressEvent(keyEvent), true;
}
if (event->type() == QEvent::KeyRelease) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (!settingsWindow->hasFocus())
return keyReleaseEvent(keyEvent), true;
}
return QMainWindow::eventFilter(obj, event);
}