276 lines
9.3 KiB
C++
276 lines
9.3 KiB
C++
#include <KaizenGui.hpp>
|
|
#include <backend/Core.hpp>
|
|
#include <ImGuiImpl/GUI.hpp>
|
|
#include <ImGuiImpl/StatusBar.hpp>
|
|
#include <resources/gamecontrollerdb.h>
|
|
|
|
KaizenGui::KaizenGui() noexcept : window("Kaizen", 800, 600), settingsWindow(window), core(std::make_shared<n64::Core>(static_cast<n64::Core::CPUType>(settingsWindow.cpuSettings.GetCPUType()))), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow) {
|
|
gui::Initialize(core->parallel.wsi, window.getHandle());
|
|
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
|
|
|
SDL_AddGamepadMapping(gamecontrollerdb_str);
|
|
}
|
|
|
|
KaizenGui::~KaizenGui() {
|
|
gui::Cleanup();
|
|
SDL_Quit();
|
|
}
|
|
|
|
void KaizenGui::QueryDevices(SDL_Event event) {
|
|
switch (event.type) {
|
|
case SDL_EVENT_GAMEPAD_ADDED:
|
|
{
|
|
const auto index = event.gdevice.which;
|
|
|
|
gamepad = SDL_OpenGamepad(index);
|
|
info("Found controller!");
|
|
}
|
|
break;
|
|
case SDL_EVENT_GAMEPAD_REMOVED:
|
|
SDL_CloseGamepad(gamepad);
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void KaizenGui::HandleInput(SDL_Event event) {
|
|
n64::PIF &pif = core->cpu->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 /= 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 /= float(std::abs(SDL_JOYSTICK_AXIS_MIN));
|
|
} else {
|
|
yclamped /= SDL_JOYSTICK_AXIS_MAX;
|
|
}
|
|
|
|
yclamped *= 86;
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::Y, -yclamped);
|
|
pif.UpdateAxis(0, n64::Controller::Axis::X, 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:
|
|
if(gamepad)
|
|
break;
|
|
{
|
|
auto keys = SDL_GetKeyboardState(nullptr);
|
|
if((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
|
|
fileDialogOpen = true;
|
|
}
|
|
|
|
if(core->romLoaded) {
|
|
if(keys[SDL_SCANCODE_P]) {
|
|
emuThread.TogglePause();
|
|
}
|
|
|
|
if(keys[SDL_SCANCODE_R]) {
|
|
emuThread.Reset();
|
|
}
|
|
|
|
if(keys[SDL_SCANCODE_Q]) {
|
|
emuThread.Stop();
|
|
}
|
|
}
|
|
|
|
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]);
|
|
pif.UpdateAxis(0, n64::Controller::Axis::Y, keys[SDL_SCANCODE_UP] ? 86 : keys[SDL_SCANCODE_DOWN] ? -86 : 0);
|
|
pif.UpdateAxis(0, n64::Controller::Axis::X, keys[SDL_SCANCODE_LEFT] ? -86 : keys[SDL_SCANCODE_RIGHT] ? 86 : 0);
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void KaizenGui::RenderUI() {
|
|
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;
|
|
}
|
|
|
|
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();
|
|
|
|
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.");
|
|
if(ImGui::Button("OK")) {
|
|
aboutOpen = false;
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
if(ImGui::BeginMainStatusBar()) {
|
|
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
|
|
ImGui::EndMainStatusBar();
|
|
}
|
|
|
|
ImGui::Render();
|
|
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable){
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
}
|
|
|
|
if(fileDialogOpen) {
|
|
fileDialogOpen = false;
|
|
const 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 filter) {
|
|
KaizenGui* kaizen = (KaizenGui*)userdata;
|
|
|
|
if (!filelist) {
|
|
panic("An error occured: %s", SDL_GetError());
|
|
return;
|
|
} else if (!*filelist) {
|
|
warn("The user did not select any file.");
|
|
warn("Most likely, the dialog was canceled.");
|
|
return;
|
|
}
|
|
|
|
kaizen->LoadROM(*filelist);
|
|
}, this, window.getHandle(), filters, 3, nullptr, false);
|
|
}
|
|
|
|
if(core->romLoaded) {
|
|
core->parallel.UpdateScreen(*core.get());
|
|
return;
|
|
}
|
|
|
|
core->parallel.UpdateScreen(*core.get(), false);
|
|
}
|
|
|
|
void KaizenGui::LoadROM(const std::string &path) noexcept {
|
|
emuThread.core->LoadROM(path);
|
|
const auto gameNameDB = emuThread.core->cpu->GetMem().rom.gameNameDB;
|
|
}
|
|
|
|
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;
|
|
}
|
|
QueryDevices(e);
|
|
HandleInput(e);
|
|
}
|
|
|
|
emuThread.run();
|
|
RenderUI();
|
|
}
|
|
}
|
|
|
|
void KaizenGui::LoadTAS(const std::string &path) const noexcept {
|
|
if (emuThread.core->LoadTAS(fs::path(path))) {
|
|
const auto gameNameDB = emuThread.core->cpu->GetMem().rom.gameNameDB;
|
|
const auto movieName = fs::path(path).stem().string();
|
|
return;
|
|
}
|
|
|
|
panic("Could not load TAS movie {}!", path);
|
|
} |