Window shows up!

This commit is contained in:
2026-06-05 17:48:11 +02:00
parent 4b9b26f8af
commit 74d9831d68
21 changed files with 433 additions and 709 deletions
+31 -338
View File
@@ -2,360 +2,53 @@
#include <backend/Core.hpp>
#include <QMenuBar>
#include <QMenu>
#include <QMessageBox>
#include <resources/gamecontrollerdb.h>
KaizenGui::KaizenGui() noexcept : vulkanWidget(windowHandle()) {
KaizenGui::KaizenGui() noexcept {
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
hide();
vulkanWidget = new RenderWidget();
setWindowTitle("Kaizen " KAIZEN_VERSION_STR);
setMinimumSize(640, 480);
setCentralWidget(vulkanWidget);
auto fileMenu = menuBar()->addMenu("File");
auto open = fileMenu->addMenu("Open");
auto exit = fileMenu->addMenu("Exit");
auto open = fileMenu->addAction("Open");
auto exit = fileMenu->addAction("Exit");
auto emulationMenu = menuBar()->addMenu("Emulation");
auto settingsMenu = emulationMenu->addMenu("Settings");
connect(settingsMenu, &QMenu::triggered, this, [&] { settingsWindow.show(); });
auto settingsMenu = emulationMenu->addAction("Settings");
settingsWindow = new SettingsWindow();
connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show);
emulationMenu->addSeparator();
auto pause = emulationMenu->addMenu("Pause");
auto reset = emulationMenu->addMenu("Reset");
auto stop = emulationMenu->addMenu("Stop");
auto pause = emulationMenu->addAction("Pause");
auto reset = emulationMenu->addAction("Reset");
auto stop = emulationMenu->addAction("Stop");
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();
}
KaizenGui::~KaizenGui() { SDL_Quit(); }
void KaizenGui::QueryDevices(const SDL_Event &event) {
switch (event.type) {
case SDL_EVENT_GAMEPAD_ADDED:
if (!gamepad) {
const auto index = event.gdevice.which;
gamepad = SDL_OpenGamepad(index);
info("Found controller!");
info("Name: {}", SDL_GetGamepadName(gamepad));
info("Vendor: {}", SDL_GetGamepadVendor(gamepad));
}
break;
case SDL_EVENT_GAMEPAD_REMOVED:
if (gamepad)
SDL_CloseGamepad(gamepad);
break;
default:
break;
}
}
void KaizenGui::HandleInput(const SDL_Event &event) {
const n64::Core &core = n64::Core::GetInstance();
n64::PIF &pif = n64::Core::GetMem().mmio.si.pif;
switch (event.type) {
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if (!gamepad)
break;
{
pif.UpdateButton(0, n64::Controller::Key::Z,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::CUp,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
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));
}
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
if (!gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
{
const auto keys = SDL_GetKeyboardState(nullptr);
if ((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
fileDialogOpen = true;
}
fastForward = keys[SDL_SCANCODE_SPACE];
if (!unlockFramerate)
core.parallel.SetFramerateUnlocked(fastForward);
if (core.romLoaded) {
if (keys[SDL_SCANCODE_P]) {
emuThread.TogglePause();
}
if (keys[SDL_SCANCODE_R]) {
emuThread.Reset();
}
if (keys[SDL_SCANCODE_Q]) {
emuThread.Stop();
}
}
if (gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::Z, keys[SDL_SCANCODE_Z]);
pif.UpdateButton(0, n64::Controller::Key::CUp, keys[SDL_SCANCODE_HOME]);
pif.UpdateButton(0, n64::Controller::Key::CDown, keys[SDL_SCANCODE_END]);
pif.UpdateButton(0, n64::Controller::Key::CLeft, keys[SDL_SCANCODE_DELETE]);
pif.UpdateButton(0, n64::Controller::Key::CRight, keys[SDL_SCANCODE_PAGEDOWN]);
pif.UpdateButton(0, n64::Controller::Key::A, keys[SDL_SCANCODE_X]);
pif.UpdateButton(0, n64::Controller::Key::B, keys[SDL_SCANCODE_C]);
pif.UpdateButton(0, n64::Controller::Key::Start, keys[SDL_SCANCODE_RETURN]);
pif.UpdateButton(0, n64::Controller::Key::DUp, keys[SDL_SCANCODE_I]);
pif.UpdateButton(0, n64::Controller::Key::DDown, keys[SDL_SCANCODE_K]);
pif.UpdateButton(0, n64::Controller::Key::DLeft, keys[SDL_SCANCODE_J]);
pif.UpdateButton(0, n64::Controller::Key::DRight, keys[SDL_SCANCODE_L]);
pif.UpdateButton(0, n64::Controller::Key::LT, keys[SDL_SCANCODE_A]);
pif.UpdateButton(0, n64::Controller::Key::RT, keys[SDL_SCANCODE_S]);
float x = 0, y = 0;
if (keys[SDL_SCANCODE_UP])
y = 86;
if (keys[SDL_SCANCODE_DOWN])
y = -86;
if (keys[SDL_SCANCODE_LEFT])
x = -86;
if (keys[SDL_SCANCODE_RIGHT])
x = 86;
pif.UpdateAxis(0, n64::Controller::Axis::X, x);
pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
}
break;
default:
break;
}
}
void KaizenGui::RenderUI() {
n64::Core &core = n64::Core::GetInstance();
gui::StartFrame();
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl-O")) {
fileDialogOpen = true;
}
if (ImGui::MenuItem("Exit")) {
quit = true;
emuThread.Stop();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Emulation")) {
ImGui::BeginDisabled(!core.romLoaded);
if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", "P")) {
emuThread.TogglePause();
}
if (ImGui::MenuItem("Reset", "R")) {
emuThread.Reset();
}
if (ImGui::MenuItem("Stop", "Q")) {
emuThread.Stop();
core.romLoaded = false;
}
if (ImGui::Checkbox("Unlock framerate", &unlockFramerate)) {
core.parallel.SetFramerateUnlocked(unlockFramerate);
}
if (ImGui::MenuItem("Open Debugger")) {
debugger.Open();
}
ImGui::EndDisabled();
if (ImGui::MenuItem("Options")) {
settingsWindow.isOpen = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help")) {
if (ImGui::MenuItem("About")) {
aboutOpen = true;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if (settingsWindow.isOpen) {
ImGui::OpenPopup("Settings", ImGuiPopupFlags_None);
}
if (aboutOpen) {
ImGui::OpenPopup("About Kaizen");
}
settingsWindow.render();
debugger.render();
const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Kaizen is a Nintendo 64 emulator that strives");
ImGui::Text("to offer a friendly user experience and compatibility.");
ImGui::Text("Kaizen is licensed under the BSD 3-clause license.");
ImGui::Text("Nintendo 64 is a registered trademark of Nintendo Co., Ltd.");
ImGui::Separator();
ImGui::Text("Kaizen %s%s", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
ImGui::Separator();
if (ImGui::Button("OK")) {
aboutOpen = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginMainStatusBar()) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
ImGui::EndMainStatusBar();
}
if (shouldDisplaySpinner) {
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0,
ImVec2(0.5f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("##spinnerContainer", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
ImGui::Spinner("##spinner", 10.f, 4.f, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive]));
ImGui::SameLine();
ImGui::PushFont(nullptr, ImGui::GetStyle().FontSizeBase * 2.f);
ImGui::Text("Loading \"%s\"...", fs::path(fileToLoad).filename().string().c_str());
ImGui::PopFont();
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
ImGui::Render();
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
if (fileDialogOpen) {
fileDialogOpen = false;
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"},
{"Nintendo 64 executable", "n64;z64;v64"},
{"Nintendo 64 executable archive", "rar;tar;zip;7z"}};
SDL_ShowOpenFileDialog(
[](void *userdata, const char *const *filelist, int) {
auto kaizen = static_cast<KaizenGui *>(userdata);
if (!filelist) {
panic("An error occured: {}", SDL_GetError());
}
if (!*filelist) {
warn("The user did not select any file.");
warn("Most likely, the dialog was canceled.");
return;
}
kaizen->fileToLoad = *filelist;
kaizen->shouldDisplaySpinner = true;
std::thread fileWorker(&KaizenGui::FileWorker, kaizen);
fileWorker.detach();
},
this, window.getHandle(), filters, 3, nullptr, false);
}
if (minimized)
return;
if (core.romLoaded) {
core.parallel.UpdateScreen<true>();
return;
}
core.parallel.UpdateScreen<false>();
}
void KaizenGui::LoadROM(const std::string &path) noexcept {
n64::Core &core = n64::Core::GetInstance();
core.LoadROM(path);
const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB;
SDL_SetWindowTitle(window.getHandle(), ("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
}
void KaizenGui::run() {
while (!quit) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e);
switch (e.type) {
case SDL_EVENT_QUIT:
quit = true;
emuThread.Stop();
break;
case SDL_EVENT_WINDOW_MINIMIZED:
minimized = true;
break;
case SDL_EVENT_WINDOW_RESTORED:
minimized = false;
break;
default:
break;
}
QueryDevices(e);
HandleInput(e);
}
SDL_GetWindowSize(window.getHandle(), &width, &height);
emuThread.run();
RenderUI();
}
setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
}
void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }