From 9ce72fdea21fd72071f60233b4955978cbc7f156 Mon Sep 17 00:00:00 2001 From: Oleksandr Nemesh Date: Fri, 19 Apr 2024 00:26:49 +0300 Subject: [PATCH] added modals to ui --- src/shared/gui/gui.hpp | 1 + src/shared/gui/modal.cpp | 92 ++++++++++++ src/shared/gui/modal.hpp | 55 ++++++++ src/shared/hacks/shortcuts/shortcuts.cpp | 59 ++++++-- src/shared/menu/menu.cpp | 170 +++++++++++++---------- src/shared/openhack.hpp | 7 + 6 files changed, 298 insertions(+), 86 deletions(-) create mode 100644 src/shared/gui/modal.cpp create mode 100644 src/shared/gui/modal.hpp diff --git a/src/shared/gui/gui.hpp b/src/shared/gui/gui.hpp index 418d1e4..0fa2461 100644 --- a/src/shared/gui/gui.hpp +++ b/src/shared/gui/gui.hpp @@ -4,6 +4,7 @@ #include "themes/themes.hpp" #include "window.hpp" +#include "modal.hpp" namespace openhack::gui { /// @brief Struct containing variants of one font. diff --git a/src/shared/gui/modal.cpp b/src/shared/gui/modal.cpp new file mode 100644 index 0000000..9a65f5b --- /dev/null +++ b/src/shared/gui/modal.cpp @@ -0,0 +1,92 @@ +#include "modal.hpp" + +#include +#include "gui.hpp" + +namespace openhack::gui { + void Modal::create(const std::string &title, std::function onDraw, bool canEscapeClose) { + // Check if popup with the same title already exists + auto &popups = getPopups(); + for (auto popup: popups) { + if (popup->getTitle() == title) { + return; + } + } + + // Create new popup + auto *popup = new Modal(title, std::move(onDraw), canEscapeClose); + popups.push_back(popup); + } + + void Modal::create(const std::string &title, const std::string &message) { + create(title, [message](Modal *popup) { + gui::text("%s", message.c_str()); + if (gui::button("OK")) { + popup->close(); + } + }); + } + + void Modal::draw() { + bool isOpen = false; + ImGui::OpenPopup(m_title.c_str(), ImGuiPopupFlags_NoOpenOverExistingPopup | ImGuiPopupFlags_NoOpenOverItems); + + auto scale = config::getGlobal("UIScale"); + ImGui::SetNextWindowSizeConstraints({MIN_SIZE.x * scale, MIN_SIZE.y * scale}, + {MAX_SIZE.x * scale, MAX_SIZE.y * scale}); + + auto font = gui::getFont(); + ImGui::PushFont(font.title); + + if (ImGui::BeginPopupModal(m_title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + isOpen = true; + if (m_canEscapeClose && utils::isKeyPressed("Esc")) { + close(); + return ImGui::EndPopup(); + } + ImGui::PushFont(font.normal); + m_onDraw(this); + ImGui::PopFont(); + ImGui::EndPopup(); + } + + ImGui::PopFont(); + + // check if the popup was closed + if (!isOpen) { + auto &popups = getPopups(); + popups.erase(std::remove(popups.begin(), popups.end(), this), popups.end()); + } + } + + const std::string &Modal::getTitle() const { + return m_title; + } + + void Modal::setTitle(const std::string &title) { + m_title = title; + } + + void Modal::close() { + ImGui::CloseCurrentPopup(); + auto &popups = getPopups(); + popups.erase(std::remove(popups.begin(), popups.end(), this), popups.end()); + } + + std::vector &getPopups() { + static std::vector popups; + return popups; + } + + void drawPopups() { + for (auto popup: getPopups()) { + popup->draw(); + } + } + + void closePopups() { + for (auto popup: getPopups()) { + popup->close(); + } + } +} \ No newline at end of file diff --git a/src/shared/gui/modal.hpp b/src/shared/gui/modal.hpp new file mode 100644 index 0000000..41348fd --- /dev/null +++ b/src/shared/gui/modal.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +namespace openhack::gui { + class Modal { + public: + inline static const ImVec2 MIN_SIZE{400, 1}; // Minimum popup size + inline static const ImVec2 MAX_SIZE{600, 800}; // Maximum popup size + + /// @brief Create a new popup with a custom draw function + /// @param title The title of the popup + /// @param onDraw The function that draws the popup + /// @param canEscapeClose Whether the popup can be closed by pressing the Escape key + static void create(const std::string &title, std::function onDraw, bool canEscapeClose = true); + + /// @brief Create a new popup with a message and an OK button + /// @param title The title of the popup + /// @param message The message to display + static void create(const std::string &title, const std::string &message); + + public: + Modal(std::string title, std::function onDraw, bool canEscapeClose) + : m_title(std::move(title)), m_onDraw(std::move(onDraw)), m_canEscapeClose(canEscapeClose) {} + + /// @brief Draws the popup + void draw(); + + /// @brief Get the title of the popup + [[nodiscard]] const std::string &getTitle() const; + + /// @brief Set the title of the popup + void setTitle(const std::string &title); + + /// @brief Close the popup + void close(); + + private: + std::string m_title; + std::function m_onDraw; + bool m_canEscapeClose = true; // Whether the popup can be closed by pressing the Escape key + }; + + /// @brief Get all popups + [[nodiscard]] std::vector &getPopups(); + + /// @brief Draw all popups + void drawPopups(); + + /// @brief Close all popups + void closePopups(); +} \ No newline at end of file diff --git a/src/shared/hacks/shortcuts/shortcuts.cpp b/src/shared/hacks/shortcuts/shortcuts.cpp index 7d25c65..778ac42 100644 --- a/src/shared/hacks/shortcuts/shortcuts.cpp +++ b/src/shared/hacks/shortcuts/shortcuts.cpp @@ -12,16 +12,35 @@ namespace openhack::hacks { #ifdef PLATFORM_WINDOWS void Shortcuts::patchGame() { - bool success = win32::four_gb::patch(); - L_INFO("Patched the game to use 4GB of memory: {}", success); - MessageBox(nullptr, success ? "Patched the game to use 4GB of memory. Please restart the game." - : "Failed to patch the game. Could not write to the file.", - "4GB Patch", (success ? MB_ICONINFORMATION : MB_ICONERROR) | MB_OK); - - if (success) { - // Close the game - std::exit(0); - } + gui::Modal::create("4GB Patch", [](gui::Modal *popup) { + ImGui::TextWrapped("This patch allows the game to use 4GB, instead of only 2GB."); + ImGui::TextWrapped("It is highly recommended to install this patch, as " + "it resolves some \"Out of memory\"/\"Bad Allocation\" crashes."); + + if (gui::button("Apply Patch", {0.5, 0.f})) { + bool success = win32::four_gb::patch(); + L_INFO("Patched the game to use 4GB of memory: {}", success); + if (success) { + popup->close(); + gui::Modal::create("4GB Patch", [](gui::Modal *popup) { + ImGui::TextWrapped("Patched the game to use 4GB of memory. Please restart the game."); + if (gui::button("Restart")) { + ON_STANDALONE( std::exit(0); ) // TODO: Implement proper restart for standalone + ON_GEODE( geode::utils::game::restart(); ) + } + }); + } else { + popup->close(); + gui::Modal::create("4GB Patch", "Failed to patch the game. Could not write to the file."); + } + } + + ImGui::SameLine(0, 2); + + if (gui::button("Cancel")) { + popup->close(); + } + }); } #else @@ -32,7 +51,7 @@ namespace openhack::hacks { #endif - void Shortcuts::uncompleteLevel() { + void uncompleteLevelConfirmed() { if (gd::PlayLayer *playLayer = gd::PlayLayer::get()) { auto *level = playLayer->m_level(); auto *statsManager = gd::GameStatsManager::sharedState(); @@ -65,6 +84,24 @@ namespace openhack::hacks { } } + void Shortcuts::uncompleteLevel() { + gui::Modal::create("Uncomplete level", [](gui::Modal *popup) { + ImGui::TextWrapped("This will clear all progress from the current level (except for orbs)."); + ImGui::TextWrapped("Are you sure you want to uncomplete the level?"); + + if (gui::button("Yes", {0.5f, 0.f})) { + uncompleteLevelConfirmed(); + popup->close(); + } + + ImGui::SameLine(0, 2); + + if (gui::button("Cancel")) { + popup->close(); + } + }); + } + void Shortcuts::openOptions() { auto *options = gd::OptionsLayer::create(); auto *scene = gd::cocos2d::CCDirector::sharedDirector()->getRunningScene(); diff --git a/src/shared/menu/menu.cpp b/src/shared/menu/menu.cpp index d8fbe45..9cab8dc 100644 --- a/src/shared/menu/menu.cpp +++ b/src/shared/menu/menu.cpp @@ -104,6 +104,21 @@ namespace openhack::menu { return isOpened || !moveActions.empty(); } + void createOpenURLPopup(const std::string &url) { + gui::Modal::create("Open URL", [url](gui::Modal *popup) { + ImGui::TextWrapped("You are about to open\n%s in your browser.", url.c_str()); + ImGui::TextWrapped("Do you want to continue?"); + if (gui::button("Yes", {0.5f, 0.f})) { + utils::openURL(url.c_str()); + popup->close(); + } + ImGui::SameLine(0, 2); + if (gui::button("No")) { + popup->close(); + } + }); + } + void init() { // Make sure to initialize ImGui gui::init(); @@ -128,10 +143,10 @@ namespace openhack::menu { #endif if (gui::button("Open GitHub page")) - utils::openURL(OPENHACK_HOME_URL); + createOpenURLPopup(OPENHACK_HOME_URL); if (gui::button("Join Discord server")) - utils::openURL("https://discord.gg/QSd4jUyc45"); + createOpenURLPopup("https://discord.gg/QSd4jUyc45"); auto searchValue = config::getGlobal("searchValue", ""); gui::widthF(1.0); @@ -275,6 +290,38 @@ namespace openhack::menu { uint8_t firstRunState = 0; + void updateColors() { + // Rainbow menu + bool rainbowEnabled = config::get("menu.rainbow.enabled"); + gui::Color accentOrig; + if (rainbowEnabled) { + accentOrig = config::get("menu.color.accent"); + + // Calculate new colors + auto speed = config::get("menu.rainbow.speed"); + auto saturation = config::get("menu.rainbow.saturation"); + auto value = config::get("menu.rainbow.value"); + + float r, g, b; + ImGui::ColorConvertHSVtoRGB( + (float) ImGui::GetTime() * speed, + saturation / 100.0f, + value / 100.0f, + r, g, b); + + gui::Color primary = {r, g, b, accentOrig.a}; + config::set("menu.color.accent", primary); + } + + // Update theme + gui::setStyles(); + + // Revert rainbow menu colors + if (rainbowEnabled) { + config::set("menu.color.accent", accentOrig); + } + } + void draw() { // Calculate relative UI scale against 1080p auto resW = ImGui::GetIO().DisplaySize.x; @@ -311,50 +358,43 @@ namespace openhack::menu { #ifdef OPENHACK_STANDALONE // Check for updates if (config::getGlobal("update.available", false)) { - gui::setStyles(); - ImGui::OpenPopup("Update Available"); - config::setGlobal("update.available", false); - } - - // Update available popup - ImGui::SetNextWindowSizeConstraints(ImVec2(400, 0), ImVec2(600, 800)); - if (ImGui::BeginPopupModal("Update Available", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - gui::text("A new version of OpenHack is available!"); - gui::text("Current version: " OPENHACK_VERSION); - gui::text("Latest version: %s", config::getGlobal("update.version").c_str()); - - std::string content = "# " + config::getGlobal("update.title") + "\n" + - config::getGlobal("update.changelog"); - - ImGui::MarkdownConfig mdConfig; - mdConfig.linkCallback = markdownOpenLink; - mdConfig.tooltipCallback = nullptr; - mdConfig.imageCallback = nullptr; - mdConfig.userData = nullptr; - auto font = gui::getFont(); - mdConfig.headingFormats[0] = {font.title, true}; - mdConfig.headingFormats[1] = {font.title, false}; - mdConfig.headingFormats[2] = {font.normal, false}; - - ImGui::Markdown(content.c_str(), content.length(), mdConfig); - - if (downloadProgress) { - if (*downloadProgress == 1.0f) - gui::text("Installing update..."); - else - gui::progressBar(*downloadProgress); - } else { - if (gui::button("Download", ImVec2(0.5, 0))) { - downloadProgress = new float(0.0f); - updater::install(config::getGlobal("update.downloadUrl"), downloadProgress); - } - ImGui::SameLine(0, 0); - if (gui::button("Close")) { - ImGui::CloseCurrentPopup(); + gui::Modal::create("Update Available", [](gui::Modal* modal){ + gui::text("A new version of OpenHack is available!"); + gui::text("Current version: " OPENHACK_VERSION); + gui::text("Latest version: %s", config::getGlobal("update.version").c_str()); + + std::string content = "# " + config::getGlobal("update.title") + "\n" + + config::getGlobal("update.changelog"); + + ImGui::MarkdownConfig mdConfig; + mdConfig.linkCallback = markdownOpenLink; + mdConfig.tooltipCallback = nullptr; + mdConfig.imageCallback = nullptr; + mdConfig.userData = nullptr; + auto font = gui::getFont(); + mdConfig.headingFormats[0] = {font.title, true}; + mdConfig.headingFormats[1] = {font.title, false}; + mdConfig.headingFormats[2] = {font.normal, false}; + + ImGui::Markdown(content.c_str(), content.length(), mdConfig); + + if (downloadProgress) { + if (*downloadProgress == 1.0f) + gui::text("Installing update..."); + else + gui::progressBar(*downloadProgress); + } else { + if (gui::button("Download", ImVec2(0.5, 0))) { + downloadProgress = new float(0.0f); + updater::install(config::getGlobal("update.downloadUrl"), downloadProgress); + } + ImGui::SameLine(0, 0); + if (gui::button("Close")) { + modal->close(); + } } - } - - ImGui::EndPopup(); + }); + config::setGlobal("update.available", false); } #endif @@ -403,47 +443,27 @@ namespace openhack::menu { isAnimating = false; } - if (!isVisible()) + if (!isVisible()) { + if (!gui::getPopups().empty()) { + updateColors(); + gui::drawPopups(); + } return; + } // Show mouse cursor if the menu is open updateCursorState(); - // Rainbow menu - bool rainbowEnabled = config::get("menu.rainbow.enabled"); - gui::Color accentOrig; - if (rainbowEnabled) { - accentOrig = config::get("menu.color.accent"); - - // Calculate new colors - auto speed = config::get("menu.rainbow.speed"); - auto saturation = config::get("menu.rainbow.saturation"); - auto value = config::get("menu.rainbow.value"); - - float r, g, b; - ImGui::ColorConvertHSVtoRGB( - (float) ImGui::GetTime() * speed, - saturation / 100.0f, - value / 100.0f, - r, g, b); - - gui::Color primary = {r, g, b, accentOrig.a}; - config::set("menu.color.accent", primary); - } - - // Update theme - gui::setStyles(); - - // Revert rainbow menu colors - if (rainbowEnabled) { - config::set("menu.color.accent", accentOrig); - } + // Update colors + updateColors(); // Draw all windows for (auto &window: windows) { window.draw(); } + gui::drawPopups(); + // Auto reset window positions auto isDragging = config::getGlobal("draggingWindow", false); auto stackEnabled = config::get("menu.stackWindows"); diff --git a/src/shared/openhack.hpp b/src/shared/openhack.hpp index 84439ae..93a1dca 100644 --- a/src/shared/openhack.hpp +++ b/src/shared/openhack.hpp @@ -22,10 +22,17 @@ #ifdef OPENHACK_GEODE #include "../geode/pch.hpp" #include + +#define ON_GEODE(...) __VA_ARGS__ +#define ON_STANDALONE(...) + #else #include "../standalone/pch.hpp" +#define ON_GEODE(...) +#define ON_STANDALONE(...) __VA_ARGS__ + #endif /// @brief The namespace for the OpenHack mod.