first work on remappable inputs

This commit is contained in:
2026-05-08 14:27:37 +02:00
parent 609fa2fb08
commit ba39535acf
26 changed files with 1224 additions and 1038 deletions
+3 -2
View File
@@ -171,11 +171,12 @@ add_executable(kaizen
src/frontend/Settings/CPUSettings.cpp src/frontend/Settings/CPUSettings.cpp
src/frontend/Settings/AudioSettings.hpp src/frontend/Settings/AudioSettings.hpp
src/frontend/Settings/AudioSettings.cpp src/frontend/Settings/AudioSettings.cpp
src/frontend/NativeWindow.hpp src/frontend/Window.hpp
src/utils/Options.cpp src/utils/Options.cpp
src/utils/File.cpp src/utils/File.cpp
src/frontend/Debugger.hpp src/frontend/Debugger.hpp
src/frontend/Debugger.cpp) src/frontend/Debugger.cpp
src/frontend/Gamepad.hpp)
if (WIN32) if (WIN32)
+1
View File
@@ -13,6 +13,7 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#pragma once #pragma once
#define IMGUI_IMPL_VULKAN_USE_VOLK
//---- Define assertion handler. Defaults to calling assert(). //---- Define assertion handler. Defaults to calling assert().
// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. // - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
+45
View File
@@ -129,6 +129,50 @@ struct Controller {
return *this; return *this;
} }
static const char *as_c_str(Axis a) {
switch (a) {
case X:
return "X-Axis";
case Y:
return "Y-Axis";
break;
}
}
static const char *as_c_str(Key k) {
switch (k) {
case A:
return "A";
case B:
return "B";
case Z:
return "Z";
case Start:
return "Start";
case DUp:
return "Up";
case DDown:
return "Down";
case DLeft:
return "Left";
case DRight:
return "Right";
case CUp:
return "C-Up";
case CDown:
return "C-Down";
case CLeft:
return "C-Left";
case CRight:
return "C-Right";
case LT:
return "Left Trigger";
case RT:
return "Right Trigger";
break;
}
}
}; };
static_assert(sizeof(Controller) == 4); static_assert(sizeof(Controller) == 4);
@@ -227,6 +271,7 @@ struct PIF {
return joybusDevices[channel].accessoryType; return joybusDevices[channel].accessoryType;
} }
private: private:
void ConfigureJoyBusFrame(); void ConfigureJoyBusFrame();
}; };
+4 -7
View File
@@ -6,7 +6,8 @@ EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(
void EmuThread::run() const noexcept { void EmuThread::run() const noexcept {
n64::Core &core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
if(!core.romLoaded) return; if (!core.romLoaded)
return;
auto lastSample = std::chrono::high_resolution_clock::now(); auto lastSample = std::chrono::high_resolution_clock::now();
auto avgFps = 16.667; auto avgFps = 16.667;
@@ -38,13 +39,9 @@ void EmuThread::run() const noexcept {
} }
} }
void EmuThread::TogglePause() const noexcept { void EmuThread::TogglePause() const noexcept { n64::Core::GetInstance().TogglePause(); }
n64::Core::GetInstance().TogglePause();
}
void EmuThread::Reset() const noexcept { void EmuThread::Reset() const noexcept { n64::Core::GetInstance().Reset(); }
n64::Core::GetInstance().Reset();
}
void EmuThread::Stop() const noexcept { void EmuThread::Stop() const noexcept {
n64::Core &core = n64::Core::GetInstance(); n64::Core &core = n64::Core::GetInstance();
+1 -1
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <RenderWidget.hpp> #include <RenderWidget.hpp>
#include <SettingsWindow.hpp> #include <SettingsWindow.hpp>
#include <memory>
namespace n64 { namespace n64 {
struct Core; struct Core;
@@ -9,6 +8,7 @@ struct Core;
class EmuThread final { class EmuThread final {
bool started = false; bool started = false;
public: public:
explicit EmuThread(double &, SettingsWindow &) noexcept; explicit EmuThread(double &, SettingsWindow &) noexcept;
~EmuThread() = default; ~EmuThread() = default;
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <variant>
#include <PIF.hpp>
#include <Options.hpp>
#include <SDL3/SDL_gamepad.h>
struct Gamepad {
private:
struct MapEntry {
bool isAxis;
int sdlVal;
std::variant<n64::Controller::Key, n64::Controller::Axis> emuVal;
const char *as_c_str() {
if (std::holds_alternative<n64::Controller::Key>(emuVal)) {
switch (std::get<n64::Controller::Key>(emuVal)) {
case n64::Controller::A:
return "A";
case n64::Controller::B:
return "B";
case n64::Controller::Z:
return "Z";
case n64::Controller::Start:
return "Start";
case n64::Controller::DUp:
return "DUp";
case n64::Controller::DDown:
return "DDown";
case n64::Controller::DLeft:
return "DLeft";
case n64::Controller::DRight:
return "DRight";
case n64::Controller::CUp:
return "CUp";
case n64::Controller::CDown:
return "CDown";
case n64::Controller::CLeft:
return "CLeft";
case n64::Controller::CRight:
return "CRight";
case n64::Controller::LT:
return "LT";
case n64::Controller::RT:
return "RT";
}
}
switch (std::get<n64::Controller::Axis>(emuVal)) {
case n64::Controller::X:
return "X";
case n64::Controller::Y:
return "Y";
}
}
} entries[14] = {
{false, SDL_GAMEPAD_BUTTON_SOUTH, n64::Controller::A},
{false, SDL_GAMEPAD_BUTTON_WEST, n64::Controller::B},
{false, SDL_GAMEPAD_BUTTON_START, n64::Controller::Start},
{false, SDL_GAMEPAD_BUTTON_DPAD_UP, n64::Controller::DUp},
{false, SDL_GAMEPAD_BUTTON_DPAD_DOWN, n64::Controller::DDown},
{false, SDL_GAMEPAD_BUTTON_DPAD_LEFT, n64::Controller::DLeft},
{false, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, n64::Controller::DRight},
{false, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, n64::Controller::LT},
{false, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, n64::Controller::RT},
{false, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, n64::Controller::Z},
{true, SDL_GAMEPAD_AXIS_RIGHTX, n64::Controller::X},
{true, SDL_GAMEPAD_AXIS_RIGHTY, n64::Controller::Y},
{true, SDL_GAMEPAD_AXIS_LEFTX, n64::Controller::X},
{true, SDL_GAMEPAD_AXIS_LEFTY, n64::Controller::Y},
};
public:
void SetValue(const MapEntry &);
void Serialize() {
auto &options = Options::GetInstance();
for(const auto& entry : entries) {
options.SetValue<int>(, const std::string &field, const T &value)
}
}
};
-2
View File
@@ -1,10 +1,8 @@
#pragma once #pragma once
#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
#include <imgui.h> #include <imgui.h>
#include <imgui_impl_sdl3.h> #include <imgui_impl_sdl3.h>
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
#include <utils/log.hpp> #include <utils/log.hpp>
#include <memory>
namespace gui { namespace gui {
static VkAllocationCallbacks *g_Allocator = NULL; static VkAllocationCallbacks *g_Allocator = NULL;
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <cstdlib>
namespace ImGui { namespace ImGui {
inline bool Spinner(const char *label, const float radius, const int thickness, const ImU32 &color) { inline bool Spinner(const char *label, const float radius, const int thickness, const ImU32 &color) {
@@ -24,7 +25,7 @@ inline bool Spinner(const char* label, const float radius, const int thickness,
window->DrawList->PathClear(); window->DrawList->PathClear();
constexpr int num_segments = 30; constexpr int num_segments = 30;
const int start = abs(ImSin(g.Time*1.8f)*(num_segments-5)); const int start = std::abs(ImSin(g.Time * 1.8f) * (num_segments - 5));
const float a_min = IM_PI * 2.0f * static_cast<float>(start) / static_cast<float>(num_segments); const float a_min = IM_PI * 2.0f * static_cast<float>(start) / static_cast<float>(num_segments);
constexpr float a_max = IM_PI * 2.0f * (static_cast<float>(num_segments) - 3) / static_cast<float>(num_segments); constexpr float a_max = IM_PI * 2.0f * (static_cast<float>(num_segments) - 3) / static_cast<float>(num_segments);
@@ -33,11 +34,11 @@ inline bool Spinner(const char* label, const float radius, const int thickness,
for (int i = 0; i < num_segments; i++) { for (int i = 0; i < num_segments; i++) {
const float a = a_min + static_cast<float>(i) / static_cast<float>(num_segments) * (a_max - a_min); const float a = a_min + static_cast<float>(i) / static_cast<float>(num_segments) * (a_max - a_min);
window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a+g.Time*8) * radius, window->DrawList->PathLineTo(
centre.y + ImSin(a+g.Time*8) * radius)); ImVec2(centre.x + ImCos(a + g.Time * 8) * radius, centre.y + ImSin(a + g.Time * 8) * radius));
} }
window->DrawList->PathStroke(color, false, thickness); window->DrawList->PathStroke(color, false, thickness);
return true; return true;
} }
} } // namespace ImGui
+16 -11
View File
@@ -3,19 +3,22 @@
#include <imgui_internal.h> #include <imgui_internal.h>
namespace ImGui { namespace ImGui {
inline bool BeginMainStatusBar() inline bool BeginMainStatusBar() {
{
ImGuiContext &g = *GetCurrentContext(); ImGuiContext &g = *GetCurrentContext();
ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)GetMainViewport(); ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)GetMainViewport();
// Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
SetCurrentViewport(NULL, viewport); SetCurrentViewport(NULL, viewport);
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be
// visible on a TV set.
// FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
// FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); // calibration in OS settings.
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; g.NextWindowData.MenuBarOffsetMinVal = ImVec2(
g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
ImGuiWindowFlags window_flags =
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
float height = GetFrameHeight(); float height = GetFrameHeight();
bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags); bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags);
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
@@ -27,16 +30,18 @@ inline bool BeginMainStatusBar()
return is_open; return is_open;
} }
inline void EndMainStatusBar() inline void EndMainStatusBar() {
{
EndMenuBar(); EndMenuBar();
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus
// to the previous window
// FIXME: With this strategy we won't be able to restore a NULL focus. // FIXME: With this strategy we won't be able to restore a NULL focus.
ImGuiContext &g = *GImGui; ImGuiContext &g = *GImGui;
if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL,
ImGuiFocusRequestFlags_UnlessBelowModal |
ImGuiFocusRequestFlags_RestoreFocusedChild);
End(); End();
} }
} } // namespace ImGui
+63 -34
View File
@@ -5,7 +5,9 @@
#include <ImGuiImpl/StatusBar.hpp> #include <ImGuiImpl/StatusBar.hpp>
#include <resources/gamecontrollerdb.h> #include <resources/gamecontrollerdb.h>
KaizenGui::KaizenGui() noexcept : window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), vulkanWidget(window.getHandle()), emuThread(fpsCounter, settingsWindow) { KaizenGui::KaizenGui() noexcept :
window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), vulkanWidget(window.getHandle()),
emuThread(fpsCounter, settingsWindow) {
gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle()); gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle());
SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_InitSubSystem(SDL_INIT_GAMEPAD);
@@ -33,7 +35,8 @@ void KaizenGui::QueryDevices(const SDL_Event &event) {
if (gamepad) if (gamepad)
SDL_CloseGamepad(gamepad); SDL_CloseGamepad(gamepad);
break; break;
default: break; default:
break;
} }
} }
@@ -45,11 +48,17 @@ void KaizenGui::HandleInput(const SDL_Event &event) {
if (!gamepad) if (!gamepad)
break; 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::Z,
pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127); SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX ||
pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127); SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127); pif.UpdateButton(0, n64::Controller::Key::CUp,
pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127); 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); float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) { if (xclamped < 0) {
@@ -134,16 +143,21 @@ void KaizenGui::HandleInput(const SDL_Event &event) {
float x = 0, y = 0; float x = 0, y = 0;
if (keys[SDL_SCANCODE_UP]) y = 86; if (keys[SDL_SCANCODE_UP])
if (keys[SDL_SCANCODE_DOWN]) y = -86; y = 86;
if (keys[SDL_SCANCODE_LEFT]) x = -86; if (keys[SDL_SCANCODE_DOWN])
if (keys[SDL_SCANCODE_RIGHT]) x = 86; 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::X, x);
pif.UpdateAxis(0, n64::Controller::Axis::Y, y); pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
} }
break; break;
default: break; default:
break;
} }
} }
@@ -156,9 +170,9 @@ std::pair<std::optional<s64>, std::optional<Util::Error::MemoryAccess>> RenderEr
auto memoryAccess = Util::Error::GetMemoryAccess(); auto memoryAccess = Util::Error::GetMemoryAccess();
if (memoryAccess.has_value()) { if (memoryAccess.has_value()) {
const auto [is_write, size, address, written_val] = memoryAccess.value(); const auto [is_write, size, address, written_val] = memoryAccess.value();
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading", ImGui::Text("%s",
static_cast<u8>(size), address, std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading", static_cast<u8>(size),
is_write ? std::format(" (value = 0x{:X})", written_val) : "") address, is_write ? std::format(" (value = 0x{:X})", written_val) : "")
.c_str()); .c_str());
} }
@@ -260,7 +274,8 @@ void KaizenGui::RenderUI() {
if (ImGui::BeginPopupModal(Util::Error::GetSeverity().as_c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal(Util::Error::GetSeverity().as_c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
emuThread.TogglePause(); emuThread.TogglePause();
switch (Util::Error::GetSeverity().as_enum) { switch (Util::Error::GetSeverity().as_enum) {
case Util::Error::Severity::WARN: { case Util::Error::Severity::WARN:
{
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x8054eae5); ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x8054eae5);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff7be4e1); ImGui::PushStyleColor(ImGuiCol_Text, 0xff7be4e1);
ImGui::Text("Warning of type: %s", Util::Error::GetType().as_c_str()); ImGui::Text("Warning of type: %s", Util::Error::GetType().as_c_str());
@@ -270,8 +285,10 @@ void KaizenGui::RenderUI() {
RenderErrorMessageDetails(); RenderErrorMessageDetails();
if (n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) { if (n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) {
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine(); const bool ignore = ImGui::Button("Try continuing");
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine(); ImGui::SameLine();
const bool stop = ImGui::Button("Stop emulation");
ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM"); const bool chooseAnother = ImGui::Button("Choose another ROM");
if (ignore || stop || chooseAnother) { if (ignore || stop || chooseAnother) {
Util::Error::SetHandled(); Util::Error::SetHandled();
@@ -294,8 +311,10 @@ void KaizenGui::RenderUI() {
if (ImGui::Button("OK")) if (ImGui::Button("OK"))
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} break; }
case Util::Error::Severity::UNRECOVERABLE: { break;
case Util::Error::Severity::UNRECOVERABLE:
{
emuThread.Stop(); emuThread.Stop();
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff); ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf); ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
@@ -307,8 +326,10 @@ void KaizenGui::RenderUI() {
RenderErrorMessageDetails(); RenderErrorMessageDetails();
if (ImGui::Button("OK")) if (ImGui::Button("OK"))
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} break; }
case Util::Error::Severity::NON_FATAL: { break;
case Util::Error::Severity::NON_FATAL:
{
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff); ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf); ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An error has occurred!"); ImGui::Text("An error has occurred!");
@@ -318,10 +339,13 @@ void KaizenGui::RenderUI() {
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str()); ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
auto [lastPC, memoryAccess] = RenderErrorMessageDetails(); auto [lastPC, memoryAccess] = RenderErrorMessageDetails();
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine(); const bool ignore = ImGui::Button("Try continuing");
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine(); ImGui::SameLine();
const bool stop = ImGui::Button("Stop emulation");
ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM"); const bool chooseAnother = ImGui::Button("Choose another ROM");
const bool openInDebugger = lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false; const bool openInDebugger =
lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
if (ignore || stop || chooseAnother || openInDebugger) { if (ignore || stop || chooseAnother || openInDebugger) {
Util::Error::SetHandled(); Util::Error::SetHandled();
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
@@ -346,8 +370,10 @@ void KaizenGui::RenderUI() {
debugger.Open(); debugger.Open();
emuThread.Reset(); emuThread.Reset();
} }
} break; }
default: break; break;
default:
break;
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -359,7 +385,8 @@ void KaizenGui::RenderUI() {
} }
if (shouldDisplaySpinner) { if (shouldDisplaySpinner) {
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0, ImVec2(0.5f, 0.5f)); 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::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
@@ -386,8 +413,11 @@ void KaizenGui::RenderUI() {
if (fileDialogOpen) { if (fileDialogOpen) {
fileDialogOpen = false; fileDialogOpen = false;
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"}, {"Nintendo 64 executable", "n64;z64;v64"}, {"Nintendo 64 executable archive", "rar;tar;zip;7z"}}; constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"},
SDL_ShowOpenFileDialog([](void *userdata, const char * const *filelist, int) { {"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); auto kaizen = static_cast<KaizenGui *>(userdata);
if (!filelist) { if (!filelist) {
@@ -405,7 +435,8 @@ void KaizenGui::RenderUI() {
std::thread fileWorker(&KaizenGui::FileWorker, kaizen); std::thread fileWorker(&KaizenGui::FileWorker, kaizen);
fileWorker.detach(); fileWorker.detach();
}, this, window.getHandle(), filters, 3, nullptr, false); },
this, window.getHandle(), filters, 3, nullptr, false);
} }
if (minimized) if (minimized)
@@ -455,6 +486,4 @@ void KaizenGui::run() {
} }
} }
void KaizenGui::LoadTAS(const std::string &path) noexcept { void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }
n64::Core::GetInstance().LoadTAS(fs::path(path));
}
+5 -2
View File
@@ -1,12 +1,14 @@
#pragma once #pragma once
#include <RenderWidget.hpp> #include <RenderWidget.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
#include <Debugger.hpp> #include <Debugger.hpp>
#include <EmuThread.hpp> #include <EmuThread.hpp>
#include <SDL3/SDL_gamepad.h> #include <SDL3/SDL_gamepad.h>
#include <Gamepad.hpp>
class KaizenGui final { class KaizenGui final {
gui::NativeWindow window; gui::Window window;
public: public:
explicit KaizenGui() noexcept; explicit KaizenGui() noexcept;
~KaizenGui(); ~KaizenGui();
@@ -26,6 +28,7 @@ public:
void run(); void run();
static void LoadTAS(const std::string &path) noexcept; static void LoadTAS(const std::string &path) noexcept;
void LoadROM(const std::string &path) noexcept; void LoadROM(const std::string &path) noexcept;
private: private:
int width{}, height{}; int width{}, height{};
bool aboutOpen = false; bool aboutOpen = false;
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <memory>
#include <volk.h>
#include <utils/log.hpp>
namespace gui {
struct NativeWindow {
NativeWindow(const std::string& title, int w, int h, int posX = SDL_WINDOWPOS_CENTERED, int posY = SDL_WINDOWPOS_CENTERED) {
SDL_Init(SDL_INIT_VIDEO);
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
window = SDL_CreateWindow(title.c_str(), w * scale, h * scale, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
if(volkInitialize() != VK_SUCCESS) {
panic("Failed to initialize Volk!");
}
}
~NativeWindow() {
SDL_DestroyWindow(window);
}
SDL_Window* getHandle() { return window; }
private:
SDL_Window* window;
};
}
+1
View File
@@ -66,6 +66,7 @@ public:
SDL_Window *window{}; SDL_Window *window{};
VkSurfaceKHR surface; VkSurfaceKHR surface;
private: private:
bool gamepadConnected = false; bool gamepadConnected = false;
}; };
+5 -3
View File
@@ -3,13 +3,14 @@
#include <imgui.h> #include <imgui.h>
#include <log.hpp> #include <log.hpp>
GeneralSettings::GeneralSettings(gui::NativeWindow& window) : window(window) { GeneralSettings::GeneralSettings(gui::Window &window) : window(window) {
savesPath = Options::GetInstance().GetValue<std::string>("general", "savePath"); savesPath = Options::GetInstance().GetValue<std::string>("general", "savePath");
} }
void GeneralSettings::render() { void GeneralSettings::render() {
if (ImGui::Button("Pick...")) { if (ImGui::Button("Pick...")) {
SDL_ShowOpenFolderDialog([](void *userdata, const char * const *filelist, int _) { SDL_ShowOpenFolderDialog(
[](void *userdata, const char *const *filelist, int _) {
auto *general = static_cast<GeneralSettings *>(userdata); auto *general = static_cast<GeneralSettings *>(userdata);
if (!filelist) { if (!filelist) {
@@ -26,7 +27,8 @@ void GeneralSettings::render() {
general->savesPath = fs::absolute(*filelist).string(); general->savesPath = fs::absolute(*filelist).string();
Options::GetInstance().SetValue<std::string>("general", "savePath", general->savesPath); Options::GetInstance().SetValue<std::string>("general", "savePath", general->savesPath);
general->modified = true; general->modified = true;
}, this, window.getHandle(), nullptr, false); },
this, window.getHandle(), nullptr, false);
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(); ImGui::BeginDisabled();
+4 -3
View File
@@ -1,11 +1,12 @@
#pragma once #pragma once
#include <SettingsTab.hpp> #include <SettingsTab.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
struct GeneralSettings final : SettingsTab { struct GeneralSettings final : SettingsTab {
void render() override; void render() override;
explicit GeneralSettings(gui::NativeWindow&); explicit GeneralSettings(gui::Window &);
private: private:
gui::NativeWindow& window; gui::Window &window;
std::string savesPath; std::string savesPath;
}; };
+3 -3
View File
@@ -2,11 +2,10 @@
#include <AudioSettings.hpp> #include <AudioSettings.hpp>
#include <CPUSettings.hpp> #include <CPUSettings.hpp>
#include <GeneralSettings.hpp> #include <GeneralSettings.hpp>
#include <NativeWindow.hpp> #include <Window.hpp>
#include <vector> #include <vector>
class SettingsWindow final { class SettingsWindow final {
gui::NativeWindow& window;
GeneralSettings generalSettings; GeneralSettings generalSettings;
CPUSettings cpuSettings; CPUSettings cpuSettings;
AudioSettings audioSettings; AudioSettings audioSettings;
@@ -17,10 +16,11 @@ class SettingsWindow final {
{"CPU", &cpuSettings}, {"CPU", &cpuSettings},
{"Audio", &audioSettings}, {"Audio", &audioSettings},
}; };
public: public:
bool isOpen = false; bool isOpen = false;
bool render(); bool render();
explicit SettingsWindow(gui::NativeWindow& window) : window(window), generalSettings(window) {} explicit SettingsWindow(gui::Window &window) : generalSettings(window) {}
[[nodiscard]] float getVolumeL() const { return audioSettings.volumeL / 100.f; } [[nodiscard]] float getVolumeL() const { return audioSettings.volumeL / 100.f; }
[[nodiscard]] float getVolumeR() const { return audioSettings.volumeR / 100.f; } [[nodiscard]] float getVolumeR() const { return audioSettings.volumeR / 100.f; }
}; };
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <utils/log.hpp>
namespace gui {
struct Window {
Window(const std::string &title, int w, int h, int posX = SDL_WINDOWPOS_CENTERED,
int posY = SDL_WINDOWPOS_CENTERED) {
SDL_Init(SDL_INIT_VIDEO);
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
window = SDL_CreateWindow(title.c_str(), w * scale, h * scale,
SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
}
~Window() { SDL_DestroyWindow(window); }
SDL_Window *getHandle() { return window; }
private:
SDL_Window *window;
};
} // namespace gui
+4 -2
View File
@@ -4,8 +4,10 @@
int main(const int argc, char **argv) { int main(const int argc, char **argv) {
KaizenGui kaizenGui; KaizenGui kaizenGui;
cflags::cflags flags; cflags::cflags flags;
flags.add_string_callback('\0', "rom", [&kaizenGui](const std::string& v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line"); flags.add_string_callback(
flags.add_string_callback('\0', "movie", [](const std::string& v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay"); '\0', "rom", [&kaizenGui](const std::string &v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line");
flags.add_string_callback(
'\0', "movie", [](const std::string &v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay");
if (!flags.parse(argc, argv)) { if (!flags.parse(argc, argv)) {
return -1; return -1;
+10
View File
@@ -10,6 +10,11 @@ void Options::SetValue<float>(const std::string &key, const std::string &field,
structure[key][field] = std::format("{:.2f}", value); structure[key][field] = std::format("{:.2f}", value);
} }
template <>
void Options::SetValue<int>(const std::string &key, const std::string &field, const int &value) {
structure[key][field] = std::format("{}", value);
}
template <> template <>
void Options::SetValue<bool>(const std::string &key, const std::string &field, const bool &value) { void Options::SetValue<bool>(const std::string &key, const std::string &field, const bool &value) {
structure[key][field] = value ? "true" : "false"; structure[key][field] = value ? "true" : "false";
@@ -25,6 +30,11 @@ float Options::GetValue<float>(const std::string &key, const std::string &field)
return std::stof(structure[key][field]); return std::stof(structure[key][field]);
} }
template <>
int Options::GetValue<int>(const std::string &key, const std::string &field) {
return std::stoi(structure[key][field]);
}
template <> template <>
bool Options::GetValue<bool>(const std::string &key, const std::string &field) { bool Options::GetValue<bool>(const std::string &key, const std::string &field) {
return structure[key][field] == "true" ? true : false; return structure[key][field] == "true" ? true : false;
+18 -3
View File
@@ -15,12 +15,26 @@ struct Options {
return; return;
} }
structure["general"]["savePath"] = "saves"; if (!fs::exists("saves"))
fs::create_directory("saves"); fs::create_directory("saves");
structure["cpu"]["type"] = "interpreter";
structure["audio"]["lock"] = "true";
structure["audio"]["volumeL"] = "0.5"; structure["audio"]["volumeL"] = "0.5";
structure["audio"]["volumeR"] = "0.5"; structure["audio"]["volumeR"] = "0.5";
structure["audio"]["lock"] = "true"; structure["general"]["savePath"] = "saves";
structure["cpu"]["type"] = "interpreter"; structure["gamepad"]["A"] = "SDL_GAMEPAD_BUTTON_SOUTH";
structure["gamepad"]["B"] = "SDL_GAMEPAD_BUTTON_WEST";
structure["gamepad"]["Start"] = "SDL_GAMEPAD_BUTTON_START";
structure["gamepad"]["DUp"] = "SDL_GAMEPAD_BUTTON_DPAD_UP";
structure["gamepad"]["DDown"] = "SDL_GAMEPAD_BUTTON_DPAD_DOWN";
structure["gamepad"]["DLeft"] = "SDL_GAMEPAD_BUTTON_DPAD_LEFT";
structure["gamepad"]["DRight"] = "SDL_GAMEPAD_BUTTON_DPAD_RIGHT";
structure["gamepad"]["LT"] = "SDL_GAMEPAD_BUTTON_LEFT_SHOULDER";
structure["gamepad"]["RT"] = "SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER";
structure["gamepad"]["Z"] = "SDL_GAMEPAD_AXIS_LEFT_TRIGGER";
structure["gamepad"]["X"] = "SDL_GAMEPAD_AXIS_RIGHTX";
structure["gamepad"]["Y"] = "SDL_GAMEPAD_AXIS_RIGHTY";
if (!file.generate(structure)) if (!file.generate(structure))
panic("Couldn't generate settings' INI!"); panic("Couldn't generate settings' INI!");
@@ -40,6 +54,7 @@ struct Options {
if (!file.write(structure)) if (!file.write(structure))
panic("Could not modify options on disk!"); panic("Could not modify options on disk!");
} }
private: private:
mINI::INIFile file; mINI::INIFile file;
mINI::INIStructure structure; mINI::INIStructure structure;