diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 3c5e3784..1d9f8ef1 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -1,9 +1,6 @@ cmake_minimum_required(VERSION 3.20) project(kaizen) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - if (WIN32) add_compile_definitions(NOMINMAX) endif () @@ -129,6 +126,7 @@ add_executable(kaizen target_link_libraries(kaizen PUBLIC imgui SDL3::SDL3 SDL3::SDL3-static cflags::cflags discord-rpc fmt mio nlohmann_json parallel-rdp capstone backend) target_compile_definitions(kaizen PUBLIC SDL_MAIN_HANDLED) +set_target_properties(kaizen PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON) file(COPY ../../resources/ DESTINATION ${PROJECT_BINARY_DIR}/resources/) file(REMOVE diff --git a/src/frontend/CPUSettings.cpp b/src/frontend/CPUSettings.cpp index 0ffbe9d8..7f363a0f 100644 --- a/src/frontend/CPUSettings.cpp +++ b/src/frontend/CPUSettings.cpp @@ -15,7 +15,7 @@ bool CPUSettings::render() { if(cpuTypes.getCurrentIndex() == 0) { JSONSetField(settings, "cpu", "type", "interpreter"); } else { - Util::panic("JIT is unfinished and currently not supported!"); + Util::panic("JIT should not be selectable??"); } modified = true; diff --git a/src/frontend/CPUSettings.hpp b/src/frontend/CPUSettings.hpp index 855de175..55b095a0 100644 --- a/src/frontend/CPUSettings.hpp +++ b/src/frontend/CPUSettings.hpp @@ -3,11 +3,12 @@ #include class CPUSettings final { - gui::Combobox cpuTypes{"CPU type:", {"Interpreter" //, "Dynamic Recompiler" - }}; + gui::Combobox cpuTypes{"CPU type:", {{"Interpreter"}, {"Dynamic Recompiler", false}}}; bool modified = false; public: bool render(); explicit CPUSettings(nlohmann::json &); + void setModified(bool v) { modified = v; } + bool getModified() { return modified; } nlohmann::json &settings; }; diff --git a/src/frontend/ImGuiImpl/Combobox.hpp b/src/frontend/ImGuiImpl/Combobox.hpp index 71866114..cdc1215b 100644 --- a/src/frontend/ImGuiImpl/Combobox.hpp +++ b/src/frontend/ImGuiImpl/Combobox.hpp @@ -1,13 +1,40 @@ #pragma once #include #include -#include +#include namespace gui { -struct Combobox { - Combobox(std::string label, std::vector items, std::string preview = "", bool enabled = true) : label(label), items(items), preview(preview), enabled(enabled) {} +struct ComboItem { + ComboItem(std::string label, bool enabled = true) : enabled(enabled) {} - void addItem(const std::string& text) { items.push_back(text); } + bool render(bool is_selected) { + ImGui::BeginDisabled(!enabled); + bool ret = ImGui::Selectable(label.c_str(), is_selected); + ImGui::EndDisabled(); + return ret; + } + + const std::string& getLabel() { return label; } + void setLabel(const std::string& text) { label = text; } +private: + bool enabled = true; + std::string label; +}; + +struct Combobox { + Combobox(std::string label, std::vector items, std::string preview = "", bool enabled = true) : label(label), items(items), preview(preview), enabled(enabled) {} + + void addItem(ComboItem& item) { + if(std::find(items.begin(), items.end(), + [&item](ComboItem& a) { return a.getLabel() == item.getLabel(); }) != items.end()) + return; + + items.push_back(item); + } + + void removeItem(const std::string& item) { + std::erase_if(items, [&item](ComboItem a) { return a.getLabel() == item; }); + } bool render() { const char* _preview = ""; @@ -15,7 +42,7 @@ struct Combobox { if(preview != "") { _preview = preview.c_str(); } else { - _preview = items[current_index].c_str(); + _preview = items[current_index].getLabel().c_str(); } bool changed = false; @@ -24,7 +51,7 @@ struct Combobox { if (ImGui::BeginCombo(label.c_str(), _preview)) { for (int n = 0; n < items.size(); n++) { const bool is_selected = ((current_index) == n); - if (ImGui::Selectable(items[n].c_str(), is_selected)) + if (items[n].render(is_selected)) { current_index = n; changed = true; @@ -41,12 +68,13 @@ struct Combobox { } void setEnabled(bool v) { enabled = v; } + bool isEnabled() { return enabled; } void setCurrentIndex(int v) { current_index = v; } int getCurrentIndex() { return current_index; } private: bool enabled = true; - std::vector& items; + std::vector items; std::string label, preview; int current_index = 0; }; diff --git a/src/frontend/ImGuiImpl/PushButton.hpp b/src/frontend/ImGuiImpl/PushButton.hpp index db47a5e3..f33aa5b5 100644 --- a/src/frontend/ImGuiImpl/PushButton.hpp +++ b/src/frontend/ImGuiImpl/PushButton.hpp @@ -21,10 +21,10 @@ struct PushButton { void setEnabled(bool v) { enabled = v; } bool getEnabled() { return enabled; } - std::string& getName() const { return name; } + const std::string& getName() { return name; } void setName(const std::string& v) { name = v; } - std::string& getLabel() const { return label; } + const std::string& getLabel() { return label; } void setLabel(const std::string& v) { label = v; } private: bool enabled = true; diff --git a/src/frontend/ImGuiImpl/TabBar.hpp b/src/frontend/ImGuiImpl/TabBar.hpp new file mode 100644 index 00000000..01acfe5f --- /dev/null +++ b/src/frontend/ImGuiImpl/TabBar.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include + +namespace gui { + +struct TabItem { + TabItem(std::string label, void(*func)(TabItem*, void* userData) = nullptr, void* userData = nullptr, bool enabled = true) : exec(func), enabled(enabled), userData(userData) {} + + bool render() { + bool ret = false; + ImGui::BeginDisabled(!enabled); + if(ImGui::BeginTabItem(label.c_str())) { + if(exec) + exec(this, userData); + ImGui::EndTabItem(); + ret = true; + } + ImGui::EndDisabled(); + return ret; + } +private: + bool enabled = true; + std::string label; + void(*exec)(TabItem*, void* userData); + void* userData; +}; + +struct TabBar { + TabBar(std::string label) : label(label) {} + void addTab(TabItem tabItem) { + tabs.push_back(tabItem); + } + + bool render() { + bool ret = false; + ImGui::BeginDisabled(!enabled); + if(ImGui::BeginTabBar(label.c_str())) { + ret = true; + for(auto& tab : tabs) { + ret &= tab.render(); + } + ImGui::EndTabBar(); + } + ImGui::EndDisabled(); + return ret; + } +private: + bool enabled = true; + std::string label; + std::vector tabs; +}; +} \ No newline at end of file diff --git a/src/frontend/InputSettings.cpp b/src/frontend/InputSettings.cpp index 589bc774..c262dcab 100644 --- a/src/frontend/InputSettings.cpp +++ b/src/frontend/InputSettings.cpp @@ -2,13 +2,14 @@ #include #include #include +#include InputSettings::InputSettings(nlohmann::json &settings) : settings(settings) { for(auto& kb : kbButtons) { kb.setLabel(JSONGetField(settings, "input", kb.getName())); } - devices.addItem("Keyboard/Mouse"); + devices.addItem({"Keyboard/Mouse"}); /* TODO: GAMEPAD STUFF IDK HOW TO HANDLE YET connect(devices.get(), &QComboBox::currentTextChanged, this, [&](const QString &text) { @@ -51,16 +52,16 @@ void InputSettings::keyPressEvent(QKeyEvent *e) { if (grabbing) { const auto k = QKeySequence(e->key()).toString().toStdString(); JSONSetField(settings, "input", buttonLabels[whichGrabbing]->text().toStdString(), k); - kbButtons[whichGrabbing]->setText(k.c_str()); - devices->setEnabled(true); + kbButtons[whichGrabbing].setText(k.c_str()); + devices.setEnabled(true); for (const auto &kbButton : kbButtons) { - kbButton->setEnabled(true); + kbButton.setEnabled(true); } grabbing = false; whichGrabbing = -1; - if (devices->currentText() == "Keyboard/Mouse") { + if (devices.currentText() == "Keyboard/Mouse") { releaseKeyboard(); emit modified(); } @@ -73,16 +74,15 @@ std::array InputSettings::GetMappedKeys() { int i = 0; - for (const auto& kb : kbButtons) { + for (auto& kb : kbButtons) { ret[i++] = SDL_GetKeyFromName(kb.getLabel().c_str()); } return ret; } -/*TODO: RECREATE THIS IN SDL void InputSettings::QueryDevices() noexcept { - if (!devices->isEnabled()) + if (!devices.isEnabled()) return; SDL_Event e; @@ -102,17 +102,17 @@ void InputSettings::QueryDevices() noexcept { if (!gamepadIndexes.contains(index)) { gamepadIndexes[index] = name; } - devices->addItem(name); + devices.addItem({name}); } else if (serial) { if (!gamepadIndexes.contains(index)) { gamepadIndexes[index] = serial; } - devices->addItem(serial); + devices.addItem({serial}); } else if (path) { if (!gamepadIndexes.contains(index)) { gamepadIndexes[index] = path; } - devices->addItem(path); + devices.addItem({path}); } SDL_CloseGamepad(gamepad); @@ -123,13 +123,14 @@ void InputSettings::QueryDevices() noexcept { const auto index = e.gdevice.which; if (gamepadIndexes.contains(index)) - devices->removeItem(devices->findText(gamepadIndexes[index].c_str())); + devices.removeItem(gamepadIndexes[index]); } break; } } } +/* void InputSettings::PollGamepad() noexcept { if (!selectedDeviceIsNotKeyboard) return; @@ -141,16 +142,16 @@ void InputSettings::PollGamepad() noexcept { { const auto k = SDL_GetGamepadStringForButton(static_cast(e.gbutton.button)); JSONSetField(settings, "input", buttonLabels[whichGrabbing]->text().toStdString(), k); - kbButtons[whichGrabbing]->setText(k); - devices->setEnabled(true); + kbButtons[whichGrabbing].setText(k); + devices.setEnabled(true); for (const auto &kbButton : kbButtons) { - kbButton->setEnabled(true); + kbButton.setEnabled(true); } grabbing = false; whichGrabbing = -1; - emit modified(); + modified = true; } break; default: diff --git a/src/frontend/InputSettings.hpp b/src/frontend/InputSettings.hpp index b4c4e0ba..c6a6d3fb 100644 --- a/src/frontend/InputSettings.hpp +++ b/src/frontend/InputSettings.hpp @@ -3,6 +3,7 @@ #include #include #include +#include class InputSettings final { bool grabbing = false; @@ -34,6 +35,7 @@ class InputSettings final { }; gui::Combobox devices{"Device:", {}}; + bool modified = false; //std::unique_ptr AB = std::make_unique(); //std::unique_ptr ZStart = std::make_unique(); @@ -56,6 +58,8 @@ public: bool render(); bool selectedDeviceIsNotKeyboard = false; explicit InputSettings(nlohmann::json &); + void setModified(bool v) { modified = v; } + bool getModified() { return modified; } nlohmann::json &settings; std::array GetMappedKeys(); }; diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index 1ca65315..223b8e4a 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -28,6 +28,18 @@ void KaizenGui::ConnectMainWindowSignalsToSlots() noexcept { connect(mainWindow.get(), &MainWindow::Pause, emuThread.get(), &EmuThread::TogglePause); } +int KaizenGui::run() { + bool inputForEmu = true; + + if(settingsWindow.render()) { + inputForEmu = false; + } + + mainWindow.render(); + + return 0; +} + void KaizenGui::LoadROM(const QString &path) const noexcept { mainWindow->actionPause->setEnabled(true); mainWindow->actionReset->setEnabled(true); diff --git a/src/frontend/SettingsWindow.cpp b/src/frontend/SettingsWindow.cpp index e91d5000..9cf0615e 100644 --- a/src/frontend/SettingsWindow.cpp +++ b/src/frontend/SettingsWindow.cpp @@ -7,66 +7,70 @@ SettingsWindow::SettingsWindow() { settings = JSONOpenOrCreate("resources/settings.json"); savePath = JSONGetField(settings, "general", "savePath"); - - if (objectName().isEmpty()) - setObjectName("Settings"); - - resize(500, 400); - setWindowTitle("Settings"); - - cpuSettings = std::make_unique(settings); - audioSettings = std::make_unique(settings); - inputSettings = std::make_unique(settings); - generalSettings = std::make_unique(); + keyMap = inputSettings.GetMappedKeys(); savesFolder.setName(fmt::format(savesFolder.getName(), savePath)); - connect(folderBtn.get(), &QPushButton::pressed, this, [&]() { - savePath = QFileDialog::getExistingDirectory(this, tr("Select directory")).toStdString(); - folderLabel->setText(fmt::format("{}", savePath).c_str()); - JSONSetField(settings, "general", "savePath", savePath); - apply->setEnabled(true); - }); - - generalLayout->addWidget(folderLabelPrefix.get()); - generalLayout->addWidget(folderLabel.get()); - generalLayout->addStretch(); - generalLayout->addWidget(folderBtn.get()); - generalLayoutV->addLayout(generalLayout.get()); - generalLayoutV->addStretch(); - generalSettings->setLayout(generalLayoutV.get()); - - tabs->addTab(generalSettings.get(), tr("General")); - tabs->addTab(cpuSettings.get(), tr("CPU")); - tabs->addTab(audioSettings.get(), tr("Audio")); - tabs->addTab(inputSettings.get(), tr("Input")); - - apply->setEnabled(false); - - connect(cpuSettings.get(), &CPUSettings::modified, this, [&]() { apply->setEnabled(true); }); - - connect(audioSettings.get(), &AudioSettings::modified, this, [&]() { apply->setEnabled(true); }); - - connect(inputSettings.get(), &InputSettings::modified, this, [&]() { apply->setEnabled(true); }); - - connect(apply.get(), &QPushButton::pressed, this, [&]() { - auto newMap = inputSettings->GetMappedKeys(); - if (!std::ranges::equal(keyMap, newMap)) { - keyMap = newMap; - emit regrabKeyboard(); + tabs.addTab({"General", [](gui::TabItem* tab, void* userData) { + SettingsWindow* sW = (SettingsWindow*)userData; + if(sW->savesFolder.render()) { + // TODO: HANDLE FILE DIALOG savePath = QFileDialog::getExistingDirectory(this, tr("Select directory")).toStdString(); + sW->savesFolder.setName(fmt::format(sW->savesFolder.getName(), savePath)); + JSONSetField(sW->settings, "general", "savePath", savePath); + sW->apply.setEnabled(true); } - apply->setEnabled(false); + }, this}); + + tabs.addTab({"CPU", [](gui::TabItem* tab, void* userData) { + SettingsWindow* sW = (SettingsWindow*)userData; + CPUSettings& cS = sW->cpuSettings; + gui::PushButton& apply = sW->apply; + + if(cS.render()) { + if(cS.getModified()) + sW->apply.setEnabled(true); + } + }, this}); + + tabs.addTab({"Audio", [](gui::TabItem* tab, void* userData) { + SettingsWindow* sW = (SettingsWindow*)userData; + AudioSettings& aS = sW->audioSettings; + gui::PushButton& apply = sW->apply; + if(aS.render()) { + if(aS.getModified()) + apply.setEnabled(true); + } + }, this}); + + tabs.addTab({"Input", [](gui::TabItem* tab, void* userData) { + SettingsWindow* sW = (SettingsWindow*)userData; + sW->inputSettings.render(); + InputSettings& iS = sW->inputSettings; + gui::PushButton& apply = sW->apply; + + if(iS.render()) { + if(iS.getModified()) + apply.setEnabled(true); + } + }, this}); + + apply.setEnabled(false); +} + +bool SettingsWindow::render() { + tabs.render(); + + if(apply.render()) { + auto newMap = inputSettings.GetMappedKeys(); + if (keyMap != newMap) + keyMap = newMap; + + apply.setEnabled(false); std::ofstream file("resources/settings.json"); file << settings; file.close(); - }); + } - connect(cancel.get(), &QPushButton::pressed, this, &QWidget::hide); - - buttonsLayout->addWidget(apply.get()); - buttonsLayout->addWidget(cancel.get()); - mainLayout->addWidget(tabs.get()); - mainLayout->addLayout(buttonsLayout.get()); - setLayout(mainLayout.get()); -} + return false; +} \ No newline at end of file diff --git a/src/frontend/SettingsWindow.hpp b/src/frontend/SettingsWindow.hpp index 01a0d166..7de70166 100644 --- a/src/frontend/SettingsWindow.hpp +++ b/src/frontend/SettingsWindow.hpp @@ -2,10 +2,15 @@ #include #include #include +#include +#include #include class SettingsWindow final { gui::PushButton cancel{"Cancel"}, apply{"Apply", "", false}, savesFolder{"Open", "Save path: {}"}; + CPUSettings cpuSettings{settings}; + AudioSettings audioSettings{settings}; + InputSettings inputSettings{settings}; //std::unique_ptr cancel = std::make_unique("Cancel"); //std::unique_ptr apply = std::make_unique("Apply"); //std::unique_ptr iconProv = std::make_unique(); @@ -18,15 +23,13 @@ class SettingsWindow final { //std::unique_ptr mainLayout = std::make_unique(); //std::unique_ptr buttonsLayout = std::make_unique(); public: - bool render() {} // TODO + bool render(); SettingsWindow(); [[nodiscard]] float getVolumeL() const { return audioSettings.volumeL.getValue() / 100.f; } [[nodiscard]] float getVolumeR() const { return audioSettings.volumeR.getValue() / 100.f; } std::array keyMap{}; nlohmann::json settings; - CPUSettings cpuSettings{settings}; - AudioSettings audioSettings{settings}; - InputSettings inputSettings{settings}; + gui::TabBar tabs{"SettingsTabs"}; //std::unique_ptr generalSettings{}; };