From 7e392e63a869baaf70f17deb8aa081dfe33f52f2 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sat, 15 Mar 2025 16:11:30 -0700 Subject: [PATCH] Modern Menu (#5116) * Add menu files, hook menu up to window system. Temporarily rename new menu's UIWidgets to UIWidgets2 to allow both menu systems to coexist temporarily. * Finish implementing new menu. Rename 2ship UIWidgets to UIWidgets2 to complete facilitation of both menus working for now. * More preliminary setup * More prepwork, begin on settings options * Finish settings, add enhancements windows * Fix search function not looking past first columns. * Add dev tool windows * Finish dev tools * Add about window * Fully replace about window * Remove moved menu items from menubar, add more windows to new menu * Implement WindowButtonOptions. Add ability to not embed popout windows when not popped out. Add ability to hide the button for WindowButtons. Fix Entrance Tracker from showing when not enabled. * Fix entrance tracker settings embedded display. Fix entrance tracker settings window original size declaration. * Initial implementation of themed radio button widget. * Move "About" section to second column of general. Fix sidebar sections starting in second column. * Restore Entrance Tracker `Draw()` to allow for custom styling. * Fix combobox positioning formatting. Fix color picker end spacing. Convert everything in check tracker settings to UIWidgets2 (except color pickers and section headers). Make all tracker windows not embed. * Minor cleanup * Fix main volume defaults & mirror jitter fix removal on dev * Improve color picker with RGB/RGBA options. Not finished. * Finish creating CVarColorPicker and implement for Check Tracker background color. Fix tracker and network prefixes. * Finish check tracker settings and convert check tracker. * Port all Cheats menu except for Beta Quest * Port over Beta Quest to new menu * Remove old cheats menu * Port cutscene skips to modern menu * Port Timesaver Enhancements to new menu * Port the Items and Item Count Messages submenu * Port Difficulty Options to new Menu * Removes options that have been ported thus far. * Port "Reduced Clutter" options to new menu * Add forgotten callbacks to Hyper Enemies/Bosses * Copy StateButton to UIWidgets2, and implement custom padding for them in the tracker. * Ports some pause menu related settinga * Change tracker window active title color. Make state buttons smaller in tracker to get more info on screen. * Convert window title active theming to all windows. * Port the rest of Enhancements->Gameplay to new menu * Port the "Graphics" Enhancements to new menu * Ports Fixes over to the Modern Menu * Ported Restorations to Modern Menu * Ported Extra Modes to new menu * Port Autosave and Boot Sequence to modern menu * Cleans up some leftover data for ported buttons * Ports Enhancement Presets to new menu * Port Additional timers to new menu. Removes Enhancements from old menu * Cleans up some unused stuff * Ports Randomizer Enhancements to modern menu * Convert Item Tracker Settings. Something's wrong with the comboboxes in a second column of a table. * Fix combobox alignment and label position calculations. * Convert Entrance Tracker window. * Save Editor Info tab finished. Added `PushStyleInput` and `PushStyleTabs` for Info tab. Fixed some indentation in entrance tracker source. Added font push to tracker windows. * Increase size of all icons in save editor. * Convert flag groups to child windows for automatic sizing and border drawing. * Flags tab completed. Finished inventory tab. * Convert save editor help hover to UIWidgets2. * Various fixes and corrections * Start cosmetics editor, fix theme colors not updating * Cosmetics editor conversion progress * Remove Mac internal resolution restrictions. * Copy over advanced resolution partial and enable most options as a custom widget. * Add size to float sliders, more cosmetics editor progress * Fix incorrect cvar for notifications * Radio button & header color options, more cosmetics editor progress * Finish cosmetics editor conversion * Create and apply THEME_COLOR macro. Resides in SohGui for easy access to mSohMenu. * Move ResolutionEditor to SohGui directory. * Add labels to build info. Fix slider width calculations. * Fix some advanced resolution widget hiding. * Fully implement Advanced Resolution options. Fix graphics settings formatting. Improve slider label position calculations. Implement Clamp options on sliders. * Finish save editor. Convert save editor code to `using namespace UIWidgets2`. Fix search crashing on time splits window. Remove `SetLastItemHoverText` from `UIWidgets2`. * Unify cvar sectioning in time splits. * Add InputString and InputInt widgets, and corresponding CVar Widgets * Adds Widget Type for Inputs, not currently used. * Convert Sail to modern menu * Add Combobox that takes a vector of std::strings * Convert checkbox and combobx to new widgets * Add Tristate checkbox * Convert sliders and tristate checkboxes * Convert top half of Rando window * remove/replace remainder of UIWidgets usage in option.cpp * Converts tricks, locations, and removes old UIWidgets refs * Fix windows build errors * Remove Tri-State checkboxes * Use PushStyleInput instead of PushStyleSlider lol oops didn't realize it was a thing in Ship. * Rebase and address review comments * Convert Crowd Control to modern menu. * Fix build error * Audio editor progress * Re-add CVar SaveOnNextFrame calls to Resolution Editor. Remove old Resolution Editor files. * Convert TimeSplits to new menu. Fix a few enum warnings. * Decrease padding on Arrow Buttons * Audio editor + gameplay stats done * Give Randomizer Menu more screen real-estate * Port plandomizer menu * Fix slider width calculation and allow combobox LabelPosition::None * Fix None labelPos and slider width for inline labels * Fix all slider value label insertions. Convert Collision Viewer. * Minor Collision Viewer enum change. Convert Actor Viewer. * Theme/convert Message Viewer. * Add font to Message Viewer. Theme Value Viewer. * DL Viewer and SohModals themed. * Convert Input Viewer. * Missed some color settings in Input Viewer. Removed UIWidgets references from Controller Config, and restored SoH version. * Remove UIWidgets.hpp include from multiple files. * Completely remove old UIWidgets. Rename UIWidgets2 to UIWidgets. Move Accessibility and Language options to new menu. * Fix Gfx Debugger not showing up. Remove menubar registration. * Fix clearCvars references. * Fix passing std::string to const char* argument. * enum name spacing --------- Co-authored-by: aMannus Co-authored-by: Christopher Leggett --- CMakeLists.txt | 5 +- .../custom/fonts/Inconsolata-Regular.ttf | Bin 0 -> 101752 bytes .../custom/fonts/Montserrat-Regular.ttf | Bin 0 -> 197624 bytes soh/include/variables.h | 6 +- soh/soh/AboutWindow.cpp | 103 - soh/soh/AboutWindow.h | 20 - .../Enhancements/TimeDisplay/TimeDisplay.h | 8 +- soh/soh/Enhancements/audio/AudioEditor.cpp | 239 +- soh/soh/Enhancements/controls/InputViewer.cpp | 253 +- .../controls/SohInputEditorWindow.cpp | 184 +- .../cosmetics/CosmeticsEditor.cpp | 757 +++--- .../Enhancements/cosmetics/CosmeticsEditor.h | 1 - .../Enhancements/debugger/MessageViewer.cpp | 20 +- soh/soh/Enhancements/debugger/actorViewer.cpp | 169 +- soh/soh/Enhancements/debugger/colViewer.cpp | 129 +- .../Enhancements/debugger/debugSaveEditor.cpp | 919 ++++--- soh/soh/Enhancements/debugger/dlViewer.cpp | 14 + soh/soh/Enhancements/debugger/valueViewer.cpp | 25 +- soh/soh/Enhancements/enhancementTypes.h | 26 +- soh/soh/Enhancements/gameplaystats.cpp | 53 +- soh/soh/Enhancements/gameplaystatswindow.h | 1 - soh/soh/Enhancements/mods.cpp | 2 +- soh/soh/Enhancements/presets.cpp | 16 +- .../Enhancements/randomizer/Plandomizer.cpp | 121 +- soh/soh/Enhancements/randomizer/Plandomizer.h | 8 +- soh/soh/Enhancements/randomizer/option.cpp | 120 +- soh/soh/Enhancements/randomizer/option.h | 12 +- .../Enhancements/randomizer/randomizer.cpp | 121 +- .../randomizer/randomizer_check_tracker.cpp | 310 +-- .../randomizer/randomizer_check_tracker.h | 1 + .../randomizer_entrance_tracker.cpp | 134 +- .../randomizer/randomizer_entrance_tracker.h | 2 +- .../randomizer/randomizer_item_tracker.cpp | 195 +- .../randomizer/randomizer_settings_window.h | 2 + soh/soh/Enhancements/randomizer/settings.cpp | 20 +- soh/soh/Enhancements/randomizer/tricks.cpp | 27 +- soh/soh/Enhancements/randomizer/tricks.h | 2 +- .../resolution-editor/ResolutionEditor.cpp | 489 ---- .../resolution-editor/ResolutionEditor.h | 16 - .../Enhancements/timesplits/TimeSplits.cpp | 299 ++- soh/soh/Network/CrowdControl/CrowdControl.cpp | 65 - soh/soh/Network/CrowdControl/CrowdControl.h | 1 - soh/soh/Network/Sail/Sail.cpp | 68 - soh/soh/Network/Sail/Sail.h | 1 - soh/soh/OTRGlobals.cpp | 48 +- soh/soh/OTRGlobals.h | 8 + soh/soh/ShipUtils.cpp | 115 + soh/soh/ShipUtils.h | 31 + soh/soh/SohGui/Menu.cpp | 819 +++++++ soh/soh/SohGui/Menu.h | 65 + soh/soh/SohGui/MenuTypes.h | 280 +++ soh/soh/SohGui/ResolutionEditor.cpp | 528 ++++ soh/soh/SohGui/ResolutionEditor.h | 12 + soh/soh/SohGui/SohGui.cpp | 82 +- soh/soh/SohGui/SohGui.hpp | 13 +- soh/soh/SohGui/SohMenu.cpp | 168 ++ soh/soh/SohGui/SohMenu.h | 227 ++ soh/soh/SohGui/SohMenuBar.cpp | 2104 +--------------- soh/soh/SohGui/SohMenuDevTools.cpp | 170 ++ soh/soh/SohGui/SohMenuEnhancements.cpp | 2146 +++++++++++++++++ soh/soh/SohGui/SohMenuNetwork.cpp | 148 ++ soh/soh/SohGui/SohMenuRandomizer.cpp | 133 + soh/soh/SohGui/SohMenuSettings.cpp | 373 +++ soh/soh/SohGui/SohModals.cpp | 9 +- soh/soh/SohGui/UIWidgets.cpp | 1947 ++++++++------- soh/soh/SohGui/UIWidgets.hpp | 1016 +++++++- soh/soh/config/ConfigMigrators.h | 6 +- soh/soh/cvar_prefixes.h | 10 +- soh/src/code/audio_playback.c | 2 +- soh/src/code/z_frame_advance.c | 5 +- 70 files changed, 9652 insertions(+), 5777 deletions(-) create mode 100644 soh/assets/custom/fonts/Inconsolata-Regular.ttf create mode 100644 soh/assets/custom/fonts/Montserrat-Regular.ttf delete mode 100644 soh/soh/AboutWindow.cpp delete mode 100644 soh/soh/AboutWindow.h delete mode 100644 soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp delete mode 100644 soh/soh/Enhancements/resolution-editor/ResolutionEditor.h create mode 100644 soh/soh/ShipUtils.cpp create mode 100644 soh/soh/ShipUtils.h create mode 100644 soh/soh/SohGui/Menu.cpp create mode 100644 soh/soh/SohGui/Menu.h create mode 100644 soh/soh/SohGui/MenuTypes.h create mode 100644 soh/soh/SohGui/ResolutionEditor.cpp create mode 100644 soh/soh/SohGui/ResolutionEditor.h create mode 100644 soh/soh/SohGui/SohMenu.cpp create mode 100644 soh/soh/SohGui/SohMenu.h create mode 100644 soh/soh/SohGui/SohMenuDevTools.cpp create mode 100644 soh/soh/SohGui/SohMenuEnhancements.cpp create mode 100644 soh/soh/SohGui/SohMenuNetwork.cpp create mode 100644 soh/soh/SohGui/SohMenuRandomizer.cpp create mode 100644 soh/soh/SohGui/SohMenuSettings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3437ef3fd..38ac6ce4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -set(CMAKE_PROJECT_GIT_COMMIT_HASH "${GIT_COMMIT_HASH}" CACHE STRING "Git commit hash" FORCE) +# Get only the first 7 characters of the hash +string(SUBSTRING "${GIT_COMMIT_HASH}" 0 7 SHORT_COMMIT_HASH) + +set(CMAKE_PROJECT_GIT_COMMIT_HASH "${SHORT_COMMIT_HASH}" CACHE STRING "Git commit hash" FORCE) execute_process( COMMAND git describe --tags --abbrev=0 --exact-match HEAD diff --git a/soh/assets/custom/fonts/Inconsolata-Regular.ttf b/soh/assets/custom/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d1241516bc2357fadd283371e34055d9b1cc89a8 GIT binary patch literal 101752 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�qnEPT11X=ckn@bDcNSYq1*MY8OvKB(de&^F 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%0A+&hMl60&72-OVAV9J+!ire=9wu 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 -#include -#include "soh/ResourceManagerHelpers.h" - -extern "C" { -#include "variables.h" -} - -AboutWindow::~AboutWindow() { - SPDLOG_TRACE("destruct about window"); -} - -void AboutWindow::InitElement() { - mIsTaggedVersion = gGitCommitTag[0] != 0; - - strncpy(mGitCommitHashTruncated, (char*)gGitCommitHash, 7); - mGitCommitHashTruncated[7] = 0; -} - -void AboutWindow::Draw() { - if (!IsVisible()) { - return; - } - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoScrollbar; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16 * ImGui::GetIO().FontGlobalScale, 8 * ImGui::GetIO().FontGlobalScale)); - - if (!ImGui::Begin(GetName().c_str(), &mIsVisible, windowFlags)) { - ImGui::End(); - } else { - DrawElement(); - ImGui::End(); - } - - ImGui::PopStyleVar(); - - // Sync up the IsVisible flag if it was changed by ImGui - SyncVisibilityConsoleVariable(); -} - -const char* AboutWindow::GetGameVersionString(uint32_t index) { - uint32_t gameVersion = ResourceMgr_GetGameVersion(index); - switch (gameVersion) { - case OOT_NTSC_US_10: - return "NTSC-U 1.0"; - case OOT_NTSC_US_11: - return "NTSC-U 1.1"; - case OOT_NTSC_US_12: - return "NTSC-U 1.2"; - case OOT_PAL_10: - return "PAL 1.0"; - case OOT_PAL_11: - return "PAL 1.1"; - case OOT_PAL_GC: - return "PAL GC"; - case OOT_PAL_MQ: - return "PAL MQ"; - case OOT_PAL_GC_DBG1: - case OOT_PAL_GC_DBG2: - return "PAL GC-D"; - case OOT_PAL_GC_MQ_DBG: - return "PAL MQ-D"; - case OOT_IQUE_CN: - return "IQUE CN"; - case OOT_IQUE_TW: - return "IQUE TW"; - default: - return "UNKNOWN"; - } -} - -void AboutWindow::DrawElement() { - // The icon is already padded - adjust for that - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - cursorPos.x -= 16 * ImGui::GetIO().FontGlobalScale; - ImGui::SetCursorScreenPos(cursorPos); - - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Game_Icon"), ImVec2(64.0f * ImGui::GetIO().FontGlobalScale, 64.0f * ImGui::GetIO().FontGlobalScale)); - - ImGui::SameLine(); - - ImGui::BeginGroup(); - ImGui::Text("Ship of Harkinian"); - if (mIsTaggedVersion) { - ImGui::Text("%s", gBuildVersion); - } else { - ImGui::Text("%s", gGitBranch); - ImGui::Text("%s", mGitCommitHashTruncated); - } - ImGui::EndGroup(); - - ImGui::Dummy(ImVec2(0, 2 * ImGui::GetIO().FontGlobalScale)); - ImGui::Text("Game Archives:"); - for (uint32_t i = 0; i < ResourceMgr_GetNumGameVersions(); i++) { - ImGui::BulletText(GetGameVersionString(i)); - } -} diff --git a/soh/soh/AboutWindow.h b/soh/soh/AboutWindow.h deleted file mode 100644 index 5a731fdc1..000000000 --- a/soh/soh/AboutWindow.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -class AboutWindow : public Ship::GuiWindow { - public: - using GuiWindow::GuiWindow; - ~AboutWindow(); - - private: - void InitElement() override; - void Draw() override; - void DrawElement() override; - void UpdateElement() override {}; - - const char* GetGameVersionString(uint32_t index); - - bool mIsTaggedVersion; - char mGitCommitHashTruncated[8]; -}; diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h index 090ac2901..671577631 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h @@ -13,20 +13,20 @@ class TimeDisplayWindow : public Ship::GuiWindow { void TimeDisplayUpdateDisplayOptions(); void TimeDisplayInitSettings(); -typedef enum { +typedef enum TimerDisplay { DISPLAY_IN_GAME_TIMER, DISPLAY_TIME_OF_DAY, DISPLAY_CONDITIONAL_TIMER, DISPLAY_NAVI_TIMER -}; +} TimerDisplay; -typedef enum { +typedef enum NaviTimerValues { NAVI_PREPARE = 600, NAVI_ACTIVE = 3000, NAVI_COOLDOWN = 25800, DAY_BEGINS = 17759, NIGHT_BEGINS = 49155 -}; +} NaviTimerValues; typedef struct { uint32_t timeID; diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index d15bc5f25..0149aa148 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -12,6 +12,7 @@ #include "soh/cvar_prefixes.h" #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "AudioCollection.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" @@ -165,13 +166,20 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence const std::string previewButton = ICON_FA_PLAY + hiddenKey; if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) == sequenceId) { - if (ImGui::Button(stopButton.c_str())) { + if (UIWidgets::Button(stopButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Stop Preview") + .Color(THEME_COLOR))) { func_800F5C2C(); CVarSetInteger(CVAR_AUDIO("Playing"), 0); } - UIWidgets::Tooltip("Stop Preview"); } else { - if (ImGui::Button(previewButton.c_str())) { + if (UIWidgets::Button(previewButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Play Preview") + .Color(THEME_COLOR))) { if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) != 0) { func_800F5C2C(); CVarSetInteger(CVAR_AUDIO("Playing"), 0); @@ -188,11 +196,10 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence } } } - UIWidgets::Tooltip("Play Preview"); } } -void Draw_SfxTab(const std::string& tabId, SeqType type) { +void Draw_SfxTab(const std::string& tabId, SeqType type, const std::string& tabName) { const std::map& map = AudioCollection::Instance->GetAllSequences(); const std::string hiddenTabId = "##" + tabId; @@ -200,7 +207,10 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { const std::string randomizeAllButton = "Randomize All" + hiddenTabId; const std::string lockAllButton = "Lock All" + hiddenTabId; const std::string unlockAllButton = "Unlock All" + hiddenTabId; - if (ImGui::Button(resetAllButton.c_str())) { + + ImGui::SeparatorText(tabName.c_str()); + if (UIWidgets::Button(resetAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); ResetGroup(map, type); @@ -211,7 +221,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(randomizeAllButton.c_str())) { + if (UIWidgets::Button(randomizeAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); RandomizeGroup(type); @@ -222,7 +233,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(lockAllButton.c_str())) { + if (UIWidgets::Button(lockAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); LockGroup(map, type); @@ -233,7 +245,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(unlockAllButton.c_str())) { + if (UIWidgets::Button(unlockAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); UnlockGroup(map, type); @@ -244,10 +257,12 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } + // Longest text in Audio Editor + ImVec2 columnSize = ImGui::CalcTextSize("Navi - Look/Hey/Watchout (Target Enemy)"); ImGui::BeginTable(tabId.c_str(), 3, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, columnSize.x + 30); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, columnSize.x + 30); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 160.0f); for (const auto& [defaultValue, seqData] : map) { if (~(seqData.category) & type) { continue; @@ -273,6 +288,7 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label.c_str())) { for (const auto& [value, seqData] : map) { // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own dropdown. @@ -293,22 +309,30 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); DrawPreviewButton((type == SEQ_SFX || type == SEQ_VOICE || type == SEQ_INSTRUMENT) ? defaultValue : currentValue, seqData.sfxKey, type); auto locked = CVarGetInteger(cvarLockKey.c_str(), 0) == 1; ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(resetButton.c_str())) { + if (UIWidgets::Button(resetButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Reset to default") + .Color(THEME_COLOR))) { CVarClear(cvarKey.c_str()); CVarClear(cvarLockKey.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); UpdateCurrentBGM(defaultValue, seqData.category); } - UIWidgets::Tooltip("Reset to default"); ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Randomize this sound") + .Color(THEME_COLOR))) { std::vector validSequences = {}; for (const auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (seqInfo->category & type) { @@ -327,10 +351,14 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { UpdateCurrentBGM(defaultValue, type); } } - UIWidgets::Tooltip("Randomize this sound"); ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(locked ? lockedButton.c_str() : unlockedButton.c_str())) { + if (UIWidgets::Button(locked ? lockedButton.c_str() : unlockedButton.c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip(locked ? "Sound locked" : "Sound unlocked") + .Color(THEME_COLOR))) { if (locked) { CVarClear(cvarLockKey.c_str()); } else { @@ -338,7 +366,6 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::Tooltip(locked ? "Sound locked" : "Sound unlocked"); } ImGui::EndTable(); } @@ -401,10 +428,11 @@ ImVec4 GetSequenceTypeColor(SeqType type) { } } -void DrawTypeChip(SeqType type) { +void DrawTypeChip(SeqType type, std::string sequenceName) { ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, GetSequenceTypeColor(type)); - ImGui::SmallButton(GetSequenceTypeName(type).c_str()); + std::string buttonLabel = GetSequenceTypeName(type) + "##" + sequenceName; + ImGui::Button(buttonLabel.c_str()); ImGui::PopStyleColor(); ImGui::EndDisabled(); } @@ -425,56 +453,68 @@ void AudioEditor::InitElement() { void AudioEditor::DrawElement() { AudioCollection::Instance->InitializeShufflePool(); - float buttonSegments = ImGui::GetContentRegionAvail().x / 4; - if (ImGui::Button("Randomize All Groups", ImVec2(buttonSegments, 30.0f))) { + UIWidgets::Separator(); + if (UIWidgets::Button("Randomize All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Randomizes all unlocked music and sound effects across tab groups"))) { AudioEditor_RandomizeAll(); } - UIWidgets::Tooltip("Randomizes all unlocked music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Reset All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Reset All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Resets all unlocked music and sound effects across tab groups"))) { AudioEditor_ResetAll(); } - UIWidgets::Tooltip("Resets all unlocked music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Lock All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Lock All Groups", UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Locks all music and sound effects across tab groups"))) { AudioEditor_LockAll(); } - UIWidgets::Tooltip("Locks all music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Unlock All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Unlock All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Unlocks all music and sound effects across tab groups"))) { AudioEditor_UnlockAll(); } - UIWidgets::Tooltip("Unlocks all music and sound effects across tab groups"); - + UIWidgets::Separator(); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Background Music")) { - Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD); + Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD, "Background Music"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Fanfares")) { - Draw_SfxTab("fanfares", SEQ_FANFARE); + Draw_SfxTab("fanfares", SEQ_FANFARE, "Fanfares"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Events")) { - Draw_SfxTab("event", SEQ_BGM_EVENT); + Draw_SfxTab("event", SEQ_BGM_EVENT, "Events"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Battle Music")) { - Draw_SfxTab("battleMusic", SEQ_BGM_BATTLE); + Draw_SfxTab("battleMusic", SEQ_BGM_BATTLE, "Battle Music"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Ocarina")) { - Draw_SfxTab("instrument", SEQ_INSTRUMENT); - Draw_SfxTab("ocarina", SEQ_OCARINA); + Draw_SfxTab("instrument", SEQ_INSTRUMENT, "Instruments"); + Draw_SfxTab("ocarina", SEQ_OCARINA, "Ocarina"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Sound Effects")) { - Draw_SfxTab("sfx", SEQ_SFX); + Draw_SfxTab("sfx", SEQ_SFX, "Sound Effects"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Voices")) { - Draw_SfxTab("voice", SEQ_VOICE); + Draw_SfxTab("voice", SEQ_VOICE, "Voices"); ImGui::EndTabItem(); } @@ -486,53 +526,63 @@ void AudioEditor::DrawElement() { ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { - ImGui::PushItemWidth(-FLT_MIN); - UIWidgets::EnhancementCheckbox("Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable")); - UIWidgets::InsertHelpHoverText( - "Disables the music change when getting close to enemies. Useful for hearing " - "your custom music for each scene more often."); - UIWidgets::EnhancementCheckbox("Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume")); - UIWidgets::InsertHelpHoverText( - "Disables the volume shifting in the Lost Woods. Useful for hearing " - "your custom music in the Lost Woods if you don't need the navigation assitance " - "the volume changing provides. If toggling this while in the Lost Woods, reload " - "the area for the effect to kick in." - ); - UIWidgets::EnhancementCheckbox("Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay")); - UIWidgets::InsertHelpHoverText( - "Displays the name of the current sequence in the corner of the screen whenever a new sequence " - "is loaded to the main sequence player (does not apply to fanfares or enemy BGM)." - ); + UIWidgets::CVarCheckbox( + "Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the music change when getting close to enemies. Useful for hearing " + "your custom music for each scene more often.")); + UIWidgets::CVarCheckbox( + "Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the volume shifting in the Lost Woods. Useful for hearing " + "your custom music in the Lost Woods if you don't need the navigation assitance " + "the volume changing provides. If toggling this while in the Lost Woods, reload " + "the area for the effect to kick in.")); + UIWidgets::CVarCheckbox( + "Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Displays the name of the current sequence in the corner of the screen whenever a new " + "sequence " + "is loaded to the main sequence player (does not apply to fanfares or enemy BGM).")); + UIWidgets::CVarSliderInt("Overlay Duration: %d seconds", CVAR_AUDIO("SeqNameOverlayDuration"), + UIWidgets::IntSliderOptions() + .Min(1) + .Max(10) + .DefaultValue(5) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderFloat("Link's voice pitch multiplier", + CVAR_AUDIO("LinkVoiceFreqMultiplier"), + UIWidgets::FloatSliderOptions() + .IsPercentage() + .Min(0.4f) + .Max(2.5f) + .DefaultValue(1.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - UIWidgets::EnhancementSliderInt("Overlay Duration: %d seconds", "##SeqNameOverlayDuration", - CVAR_AUDIO("SeqNameOverlayDuration"), 1, 10, "", 5); - ImGui::PopItemWidth(); - ImGui::NewLine(); - ImGui::PopItemWidth(); - UIWidgets::EnhancementSliderFloat("Link's voice pitch multiplier: %.1f %%", "##linkVoiceFreqMultiplier", - CVAR_AUDIO("LinkVoiceFreqMultiplier"), 0.4, 2.5, "", 1.0, true, true); - ImGui::SameLine(); - const std::string resetButton = "Reset##linkVoiceFreqMultiplier"; - if (ImGui::Button(resetButton.c_str())) { + ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 40.f); + if (UIWidgets::Button("Reset##linkVoiceFreqMultiplier", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - - ImGui::NewLine(); - UIWidgets::EnhancementCheckbox("Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene")); - UIWidgets::Tooltip("Enables randomizing all unlocked music and sound effects when you enter a new scene."); - - ImGui::NewLine(); - ImGui::PushItemWidth(-FLT_MIN); - UIWidgets::PaddedSeparator(); - UIWidgets::PaddedText("The following options are experimental and may cause music\nto sound odd or have other undesireable effects."); - UIWidgets::EnhancementCheckbox("Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop")); - UIWidgets::InsertHelpHoverText("Some custom sequences may have notes that are too high for the game's audio " - "engine to play. Enabling this checkbox will cause these notes to drop a " - "couple of octaves so they can still harmonize with the other notes of the " - "sequence."); - ImGui::PopItemWidth(); + UIWidgets::CVarCheckbox( + "Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "Enables randomizing all unlocked music and sound effects when you enter a new scene.")); + UIWidgets::CVarCheckbox( + "Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Some custom sequences may have notes that are too high for the game's audio " + "engine to play. Enabling this checkbox will cause these notes to drop a " + "couple of octaves so they can still harmonize with the other notes of the " + "sequence.")); } ImGui::EndChild(); ImGui::EndTable(); @@ -564,9 +614,12 @@ void AudioEditor::DrawElement() { std::set seqsToExclude = {}; static ImGuiTextFilter sequenceSearch; + UIWidgets::PushStyleInput(THEME_COLOR); sequenceSearch.Draw("Filter (inc,-exc)", 490.0f); + UIWidgets::PopStyleInput(); ImGui::SameLine(); - if (ImGui::Button("Exclude All")) { + if (UIWidgets::Button("Exclude All", + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToExclude.insert(seqInfo); @@ -574,7 +627,8 @@ void AudioEditor::DrawElement() { } } ImGui::SameLine(); - if (ImGui::Button("Include All")) { + if (UIWidgets::Button("Include All", + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToInclude.insert(seqInfo); @@ -643,13 +697,17 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildIncludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { - if (ImGui::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str())) { + if (UIWidgets::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(9.0f, 6.0f)) + .Color(THEME_COLOR))) { seqsToExclude.insert(seqInfo); } ImGui::SameLine(); DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); ImGui::SameLine(); - DrawTypeChip(seqInfo->category); + DrawTypeChip(seqInfo->category, seqInfo->label); ImGui::SameLine(); ImGui::Text("%s", seqInfo->label.c_str()); } @@ -667,13 +725,17 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildExcludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { - if (ImGui::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str())) { + if (UIWidgets::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(9.0f, 6.0f)) + .Color(THEME_COLOR))) { seqsToInclude.insert(seqInfo); } ImGui::SameLine(); DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); ImGui::SameLine(); - DrawTypeChip(seqInfo->category); + DrawTypeChip(seqInfo->category, seqInfo->sfxKey); ImGui::SameLine(); ImGui::Text("%s", seqInfo->label.c_str()); } @@ -695,6 +757,7 @@ void AudioEditor::DrawElement() { ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); } std::vector allTypes = { SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE }; diff --git a/soh/soh/Enhancements/controls/InputViewer.cpp b/soh/soh/Enhancements/controls/InputViewer.cpp index 5f650a25f..3eb18c0e2 100644 --- a/soh/soh/Enhancements/controls/InputViewer.cpp +++ b/soh/soh/Enhancements/controls/InputViewer.cpp @@ -10,31 +10,25 @@ #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" + +using namespace UIWidgets; // Text colors -static ImVec4 textColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); -static ImVec4 range1Color = ImVec4(1.0f, 0.7f, 0, 1.0f); -static ImVec4 range2Color = ImVec4(0, 1.0f, 0, 1.0f); +static Color_RGBA8 textColorDefault = { 255, 255, 255, 255 }; +static Color_RGBA8 range1ColorDefault = { 255, 178, 0, 255 }; +static Color_RGBA8 range2ColorDefault = { 0, 255, 0, 255 }; -static const char* buttonOutlineOptions[4] = { "Always Shown", "Shown Only While Not Pressed", - "Shown Only While Pressed", "Always Hidden" }; -static const char* buttonOutlineOptionsVerbose[4] = { "Outline Always Shown", "Outline Shown Only While Not Pressed", - "Outline Shown Only While Pressed", "Outline Always Hidden" }; +static std::unordered_map buttonOutlineOptions = + {{ BUTTON_OUTLINE_ALWAYS_SHOWN, "Always Shown" }, { BUTTON_OUTLINE_NOT_PRESSED, "Shown Only While Not Pressed" }, + { BUTTON_OUTLINE_PRESSED, "Shown Only While Pressed" }, { BUTTON_OUTLINE_ALWAYS_HIDDEN, "Always Hidden" }}; +static std::unordered_map buttonOutlineOptionsVerbose = + {{ BUTTON_OUTLINE_ALWAYS_SHOWN, "Outline Always Shown" }, { BUTTON_OUTLINE_NOT_PRESSED, "Outline Shown Only While Not Pressed" }, + { BUTTON_OUTLINE_PRESSED, "Outline Shown Only While Pressed" }, { BUTTON_OUTLINE_ALWAYS_HIDDEN, "Outline Always Hidden" }}; -static const char* stickModeOptions[3] = { "Always", "While In Use", "Never" }; - -static Color_RGBA8 vec2Color(ImVec4 vec) { - Color_RGBA8 color; - color.r = vec.x * 255.0; - color.g = vec.y * 255.0; - color.b = vec.z * 255.0; - color.a = vec.w * 255.0; - return color; -} - -static ImVec4 color2Vec(Color_RGBA8 color) { - return ImVec4(color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); -} +static std::unordered_map stickModeOptions = + {{ STICK_MODE_ALWAYS_SHOWN, "Always" }, { STICK_MODE_HIDDEN_IN_DEADZONE, "While In Use" }, { STICK_MODE_ALWAYS_HIDDEN, "Never" }}; InputViewer::~InputViewer() { SPDLOG_TRACE("destruct input viewer"); @@ -399,17 +393,17 @@ void InputViewer::DrawElement() { (rSquared >= (range1Min * range1Min)) && (rSquared < (range1Max * range1Max))) { ImGui::PushStyleColor( ImGuiCol_Text, - color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), vec2Color(range1Color)))); + VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color.Value"), range1ColorDefault))); } else if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), 0) && (rSquared >= (range2Min * range2Min)) && (rSquared < (range2Max * range2Max))) { ImGui::PushStyleColor( ImGuiCol_Text, - color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), vec2Color(range2Color)))); + VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color.Value"), range2ColorDefault))); } else { - ImGui::PushStyleColor(ImGuiCol_Text, color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), - vec2Color(textColor)))); + ImGui::PushStyleColor(ImGuiCol_Text, VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor.Value"), + textColorDefault))); } // Render text @@ -434,250 +428,217 @@ InputViewerSettingsWindow::~InputViewerSettingsWindow() { } void InputViewerSettingsWindow::DrawElement() { - // gInputViewer.Scale - UIWidgets::EnhancementSliderFloat("Input Viewer Scale: %.2f", "##Input", CVAR_INPUT_VIEWER("Scale"), 0.1f, 5.0f, "", - 1.0f, false, true); - UIWidgets::Tooltip("Sets the on screen size of the input viewer"); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + // gInputViewer.Scale + CVarSliderFloat("Input Viewer Scale: %.2f", CVAR_INPUT_VIEWER("Scale"), + FloatSliderOptions().Color(THEME_COLOR).DefaultValue(1.0f).Min(0.1f).Max(5.0f).ShowButtons(true).Tooltip("Sets the on screen size of the input viewer")); // gInputViewer.EnableDragging - UIWidgets::EnhancementCheckbox("Enable Dragging", CVAR_INPUT_VIEWER("EnableDragging"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Enable Dragging", CVAR_INPUT_VIEWER("EnableDragging"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(true, true); // gInputViewer.ShowBackground - UIWidgets::EnhancementCheckbox("Show Background Layer", CVAR_INPUT_VIEWER("ShowBackground"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Background Layer", CVAR_INPUT_VIEWER("ShowBackground"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(true, true); + PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader("Buttons")) { // gInputViewer.ButtonOutlineMode - UIWidgets::PaddedText("Button Outlines/Backgrounds", true, false); - UIWidgets::EnhancementCombobox( - CVAR_INPUT_VIEWER("ButtonOutlineMode"), buttonOutlineOptions, BUTTON_OUTLINE_NOT_PRESSED, - !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1), "", - CVarGetInteger(CVAR_INPUT_VIEWER("ButtonOutlineMode"), BUTTON_OUTLINE_NOT_PRESSED)); - UIWidgets::Tooltip( - "Sets the desired visibility behavior for the button outline/background layers. Useful for " - "custom input viewers."); + CVarCombobox("Button Outlines/Backgrounds", CVAR_INPUT_VIEWER("ButtonOutlineMode"), buttonOutlineOptions, + ComboboxOptions({{ .disabled = !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1), .disabledTooltip = "Disabled because Global Button Outline is off" }}) + .Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED) + .Tooltip("Sets the desired visibility behavior for the button outline/background layers. Useful for " + "custom input viewers.")); // gInputViewer.UseGlobalButtonOutlineMode - UIWidgets::EnhancementCheckbox("Use for all buttons", CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Use for all buttons", CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(); bool useIndividualOutlines = !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1); // gInputViewer.ABtn - UIWidgets::EnhancementCheckbox("Show A-Button Layers", CVAR_INPUT_VIEWER("ABtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show A-Button Layers", CVAR_INPUT_VIEWER("ABtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("ABtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("ABtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##ABtnOutline", CVAR_INPUT_VIEWER("ABtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.BBtn - UIWidgets::EnhancementCheckbox("Show B-Button Layers", CVAR_INPUT_VIEWER("BBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show B-Button Layers", CVAR_INPUT_VIEWER("BBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("BBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("BBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##BBtnOutline", CVAR_INPUT_VIEWER("BBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CUp - UIWidgets::EnhancementCheckbox("Show C-Up Layers", CVAR_INPUT_VIEWER("CUp"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Up Layers", CVAR_INPUT_VIEWER("CUp"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CUp"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CUpOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CUpOutline", CVAR_INPUT_VIEWER("CUpOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CRight - UIWidgets::EnhancementCheckbox("Show C-Right Layers", CVAR_INPUT_VIEWER("CRight"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Right Layers", CVAR_INPUT_VIEWER("CRight"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CRight"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CRightOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CRightOutline", CVAR_INPUT_VIEWER("CRightOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CDown - UIWidgets::EnhancementCheckbox("Show C-Down Layers", CVAR_INPUT_VIEWER("CDown"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Down Layers", CVAR_INPUT_VIEWER("CDown"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CDown"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CDownOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CDownOutline", CVAR_INPUT_VIEWER("CDownOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CLeft - UIWidgets::EnhancementCheckbox("Show C-Left Layers", CVAR_INPUT_VIEWER("CLeft"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Left Layers", CVAR_INPUT_VIEWER("CLeft"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CLeft"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CLeftOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CLeftOutline", CVAR_INPUT_VIEWER("CLeftOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.LBtn - UIWidgets::EnhancementCheckbox("Show L-Button Layers", CVAR_INPUT_VIEWER("LBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show L-Button Layers", CVAR_INPUT_VIEWER("LBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("LBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("LBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##LBtnOutline", CVAR_INPUT_VIEWER("LBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.RBtn - UIWidgets::EnhancementCheckbox("Show R-Button Layers", CVAR_INPUT_VIEWER("RBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show R-Button Layers", CVAR_INPUT_VIEWER("RBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("RBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##RBtnOutline", CVAR_INPUT_VIEWER("RBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.ZBtn - UIWidgets::EnhancementCheckbox("Show Z-Button Layers", CVAR_INPUT_VIEWER("ZBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Z-Button Layers", CVAR_INPUT_VIEWER("ZBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("ZBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("ZBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##ZBtnOutline", CVAR_INPUT_VIEWER("ZBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.StartBtn - UIWidgets::EnhancementCheckbox("Show Start Button Layers", CVAR_INPUT_VIEWER("StartBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Start Button Layers", CVAR_INPUT_VIEWER("StartBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("StartBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("StartBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##StartBtnOutline", CVAR_INPUT_VIEWER("StartBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Dpad - UIWidgets::EnhancementCheckbox("Show D-Pad Layers", CVAR_INPUT_VIEWER("Dpad"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show D-Pad Layers", CVAR_INPUT_VIEWER("Dpad"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Dpad"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("DpadOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##DpadOutline", CVAR_INPUT_VIEWER("DpadOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Mod1 - UIWidgets::EnhancementCheckbox("Show Modifier Button 1 Layers", CVAR_INPUT_VIEWER("Mod1"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show Modifier Button 1 Layers", CVAR_INPUT_VIEWER("Mod1"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Mod1"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("Mod1OutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##Mmod1Outline", CVAR_INPUT_VIEWER("Mod1OutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Mod2 - UIWidgets::EnhancementCheckbox("Show Modifier Button 2 Layers", CVAR_INPUT_VIEWER("Mod2"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show Modifier Button 2 Layers", CVAR_INPUT_VIEWER("Mod2"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Mod2"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("Mod2OutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##Mod2Outline", CVAR_INPUT_VIEWER("Mod2OutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } - UIWidgets::PaddedSeparator(true, true); + UIWidgets:PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Analog Stick")) { // gInputViewer.AnalogStick.VisibilityMode - UIWidgets::PaddedText("Analog Stick Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("AnalogStick.VisibilityMode"), stickModeOptions, - STICK_MODE_ALWAYS_SHOWN); - UIWidgets::Tooltip( - "Determines the conditions under which the moving layer of the analog stick texture is visible."); + CVarCombobox("Analog Stick Visibility", CVAR_INPUT_VIEWER("AnalogStick.VisibilityMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the moving layer of the analog stick texture is visible.")); // gInputViewer.AnalogStick.OutlineMode - UIWidgets::PaddedText("Analog Stick Outline/Background Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("AnalogStick.OutlineMode"), stickModeOptions, - STICK_MODE_ALWAYS_SHOWN); - UIWidgets::Tooltip( - "Determines the conditions under which the analog stick outline/background texture is visible."); + CVarCombobox("Analog Stick Outline/Background Visibility", CVAR_INPUT_VIEWER("AnalogStick.OutlineMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the analog stick outline/background texture is visible.")); // gInputViewer.AnalogStick.Movement - UIWidgets::EnhancementSliderInt("Analog Stick Movement: %dpx", "##AnalogMovement", - CVAR_INPUT_VIEWER("AnalogStick.Movement"), 0, 200, "", 12, true); - UIWidgets::Tooltip( - "Sets the distance to move the analog stick in the input viewer. Useful for custom input viewers."); + CVarSliderInt("Analog Stick Movement: %dpx", CVAR_INPUT_VIEWER("AnalogStick.Movement"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(200).DefaultValue(12).ShowButtons(true) + .Tooltip("Sets the distance to move the analog stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Additional (\"Right\") Stick")) { // gInputViewer.RightStick.VisibilityMode - UIWidgets::PaddedText("Right Stick Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RightStick.VisibilityMode"), stickModeOptions, - STICK_MODE_HIDDEN_IN_DEADZONE); - UIWidgets::Tooltip( - "Determines the conditions under which the moving layer of the right stick texture is visible."); + CVarCombobox("Right Stick Visibility", CVAR_INPUT_VIEWER("RightStick.VisibilityMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the moving layer of the right stick texture is visible.")); // gInputViewer.RightStick.OutlineMode - UIWidgets::PaddedText("Right Stick Outline/Background Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RightStick.OutlineMode"), stickModeOptions, - STICK_MODE_HIDDEN_IN_DEADZONE); - UIWidgets::Tooltip( - "Determines the conditions under which the right stick outline/background texture is visible."); + CVarCombobox("Right Stick Outline/Background Visibility", CVAR_INPUT_VIEWER("RightStick.OutlineMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the right stick outline/background texture is visible.")); // gInputViewer.RightStick.Movement - UIWidgets::EnhancementSliderInt("Right Stick Movement: %dpx", "##RightMovement", - CVAR_INPUT_VIEWER("RightStick.Movement"), 0, 200, "", 7, true); - UIWidgets::Tooltip( - "Sets the distance to move the right stick in the input viewer. Useful for custom input viewers."); + CVarSliderInt("Right Stick Movement: %dpx", CVAR_INPUT_VIEWER("RightStick.Movement"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(200).DefaultValue(7).ShowButtons(true) + .Tooltip("Sets the distance to move the right stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Analog Angle Values")) { // gAnalogAngles - UIWidgets::EnhancementCheckbox("Show Analog Stick Angle Values", CVAR_INPUT_VIEWER("AnalogAngles.Enabled")); - UIWidgets::Tooltip("Displays analog stick angle values in the input viewer"); + CVarCheckbox("Show Analog Stick Angle Values", CVAR_INPUT_VIEWER("AnalogAngles.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Displays analog stick angle values in the input viewer")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Enabled"), 0)) { // gInputViewer.AnalogAngles.TextColor - if (ImGui::ColorEdit4("Text Color", (float*)&textColor)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), vec2Color(textColor)); - } + CVarColorPicker("Text Color", CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), textColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); // gAnalogAngleScale - UIWidgets::EnhancementSliderFloat("Angle Text Scale: %.2f%%", "##AnalogAngleScale", - CVAR_INPUT_VIEWER("AnalogAngles.Scale"), 0.1f, 5.0f, "", 1.0f, true, true); + CVarSliderFloat("Angle Text Scale: %.2f%%", CVAR_INPUT_VIEWER("AnalogAngles.Scale"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.1f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); // gInputViewer.AnalogAngles.Offset - UIWidgets::EnhancementSliderInt("Angle Text Offset: %dpx", "##AnalogAngleOffset", - CVAR_INPUT_VIEWER("AnalogAngles.Offset"), 0, 400, "", 0, true); + CVarSliderInt("Angle Text Offset: %dpx", CVAR_INPUT_VIEWER("AnalogAngles.Offset"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(400).DefaultValue(0).ShowButtons(true) + .Tooltip("Sets the distance to move the right stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); // gInputViewer.AnalogAngles.Range1.Enabled - UIWidgets::EnhancementCheckbox("Highlight ESS Position", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled")); - UIWidgets::Tooltip( - "Highlights the angle value text when the analog stick is in ESS position (on flat ground)"); + CVarCheckbox("Highlight ESS Position", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Highlights the angle value text when the analog stick is in ESS position (on flat ground)")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled"), 0)) { // gInputViewer.AnalogAngles.Range1.Color - if (ImGui::ColorEdit4("ESS Color", (float*)&range1Color)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), vec2Color(range1Color)); - } + CVarColorPicker("ESS Color", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), range1ColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); } UIWidgets::PaddedSeparator(true, true); // gInputViewer.AnalogAngles.Range2.Enabled - UIWidgets::EnhancementCheckbox("Highlight Walking Speed Angles", - CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled")); - UIWidgets::Tooltip("Highlights the angle value text when the analog stick is at an angle that would " + CVarCheckbox("Highlight Walking Speed Angles", CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Highlights the angle value text when the analog stick is at an angle that would " "produce a walking speed (on flat ground)\n\n" - "Useful for 1.0 Empty Jumpslash Quick Put Away"); + "Useful for 1.0 Empty Jumpslash Quick Put Away")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), 0)) { // gInputViewer.AnalogAngles.Range2.Color - if (ImGui::ColorEdit4("Walking Speed Color", (float*)&range2Color)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), vec2Color(range2Color)); - } + CVarColorPicker("Walking Speed Color", CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), range2ColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); } } } + PopStyleHeader(); + ImGui::PopFont(); } diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index 7e4918799..135cb8a00 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -2,6 +2,7 @@ #include #include "soh/OTRGlobals.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "z64.h" #include "soh/cvar_prefixes.h" #ifndef __WIIU__ @@ -10,6 +11,8 @@ #define SCALE_IMGUI_SIZE(value) ((value / 13.0f) * ImGui::GetFontSize()) +using namespace UIWidgets; + SohInputEditorWindow::~SohInputEditorWindow() { } @@ -1062,17 +1065,16 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { } // todo: clean this up, probably just hardcode to LED_COLOR_SOURCE_GAME and use SoH options only here if (mapping->GetColorSource() == LED_COLOR_SOURCE_GAME) { - static const char* ledSources[] = { + static std::vector ledSources = { "Original Tunic Colors", "Cosmetics Tunic Colors", "Health Colors", "Original Navi Targeting Colors", "Cosmetics Navi Targeting Colors", "Custom" }; - UIWidgets::PaddedText("Source"); - UIWidgets::EnhancementCombobox(CVAR_SETTING("LEDColorSource"), ledSources, LED_SOURCE_TUNIC_ORIGINAL); - UIWidgets::Tooltip("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when " + CVarCombobox("Source", CVAR_SETTING("LEDColorSource"), ledSources, UIWidgets::ComboboxOptions().Color(THEME_COLOR).DefaultIndex(LED_SOURCE_TUNIC_ORIGINAL) + .Tooltip("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when " "health < 40%. Green otherwise.\n\n" "Tunics: colors will mirror currently equipped tunic, whether original or the current " "values in Cosmetics Editor.\n\n" - "Custom: single, solid color"); + "Custom: single, solid color")); if (CVarGetInteger(CVAR_SETTING("LEDColorSource"), 1) == LED_SOURCE_CUSTOM) { UIWidgets::Spacer(3); auto port1Color = CVarGetColor24(CVAR_SETTING("LEDPort1Color"), { 255, 255, 255 }); @@ -1090,14 +1092,13 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { ImGui::SameLine(); ImGui::Text("Custom Color"); } - UIWidgets::PaddedEnhancementSliderFloat("Brightness: %.1f %%", "##LED_Brightness", CVAR_SETTING("LEDBrightness"), 0.0f, - 1.0f, "", 1.0f, true, true); - UIWidgets::Tooltip("Sets the brightness of controller LEDs. 0% brightness = LEDs off."); - UIWidgets::PaddedEnhancementCheckbox( - "Critical Health Override", CVAR_SETTING("LEDCriticalOverride"), true, true, - CVarGetInteger(CVAR_SETTING("LEDColorSource"), LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, - "Override redundant for health source.", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Shows red color when health is critical, otherwise displays according to color source."); + CVarSliderFloat("Brightness: %.1f %%", CVAR_SETTING("LEDBrightness"), + FloatSliderOptions().IsPercentage().Min(0.0f).Max(1.0f).DefaultValue(1.0f).ShowButtons(true) + .Tooltip("Sets the brightness of controller LEDs. 0% brightness = LEDs off.")); + CVarCheckbox("Critical Health Override", CVAR_SETTING("LEDCriticalOverride"), + CheckboxOptions({{ .disabled = CVarGetInteger(CVAR_SETTING("LEDColorSource"), LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, + .disabledTooltip = "Override redundant for health source."}}).DefaultValue(true) + .Tooltip("Shows red color when health is critical, otherwise displays according to color source.")); } ImGui::TreePop(); } @@ -1281,7 +1282,6 @@ void SohInputEditorWindow::DrawMapping(CustomButtonMap& mapping, float labelWidt preview = "Unknown"; } - UIWidgets::Spacer(0); ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec2 textSize = ImGui::CalcTextSize(mapping.label); ImGui::SetCursorPosY(cursorPos.y + textSize.y / 4); @@ -1303,16 +1303,16 @@ void SohInputEditorWindow::DrawMapping(CustomButtonMap& mapping, float labelWidt } ImGui::EndCombo(); } - UIWidgets::Spacer(0); } void SohInputEditorWindow::DrawOcarinaControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 5)); - UIWidgets::EnhancementCheckbox("Dpad Ocarina Playback", CVAR_SETTING("CustomOcarina.Dpad")); - UIWidgets::EnhancementCheckbox("Right Stick Ocarina Playback", CVAR_SETTING("CustomOcarina.RightStick")); - UIWidgets::EnhancementCheckbox("Customize Ocarina Controls", CVAR_SETTING("CustomOcarina.Enabled")); + CheckboxOptions checkOpt = CheckboxOptions().Color(THEME_COLOR); + CVarCheckbox("Dpad Ocarina Playback", CVAR_SETTING("CustomOcarina.Dpad"), checkOpt); + CVarCheckbox("Right Stick Ocarina Playback", CVAR_SETTING("CustomOcarina.RightStick"), checkOpt); + CVarCheckbox("Customize Ocarina Controls", CVAR_SETTING("CustomOcarina.Enabled"), checkOpt); if (!CVarGetInteger(CVAR_SETTING("CustomOcarina.Enabled"), 0)) { ImGui::BeginDisabled(); @@ -1344,60 +1344,56 @@ void SohInputEditorWindow::DrawCameraControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("Aiming/First-Person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Right Stick Aiming", CVAR_SETTING("Controls.RightStickAim")); - UIWidgets::Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming"); + CVarCheckbox("Right Stick Aiming", CVAR_SETTING("Controls.RightStickAim"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming")); if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Allow moving while in first person mode", CVAR_SETTING("MoveInFirstPerson")); - UIWidgets::Tooltip("Changes the left stick to move the player while in first person mode"); + CVarCheckbox("Allow moving while in first person mode", CVAR_SETTING("MoveInFirstPerson"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Changes the left stick to move the player while in first person mode")); } - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming X Axis", CVAR_SETTING("Controls.InvertAimingXAxis")); - UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming Y Axis", CVAR_SETTING("Controls.InvertAimingYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming X Axis", CVAR_SETTING("Controls.InvertShieldAimingXAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Shield Aiming X Axis"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming Y Axis", CVAR_SETTING("Controls.InvertShieldAimingYAxis")); - UIWidgets::Tooltip("Inverts the Shield Aiming Y Axis"); - UIWidgets::PaddedEnhancementCheckbox("Invert Z-Weapon Aiming Y Axis", CVAR_SETTING("Controls.InvertZAimingYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Z-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Disable Auto-Centering in First-Person View", CVAR_SETTING("DisableFirstPersonAutoCenterView")); - UIWidgets::Tooltip("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming"); - if (UIWidgets::PaddedEnhancementCheckbox("Enable Custom Aiming/First-Person sensitivity", CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), true, false)) { + CVarCheckbox("Invert Aiming X Axis", CVAR_SETTING("Controls.InvertAimingXAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming")); + CVarCheckbox("Invert Aiming Y Axis", CVAR_SETTING("Controls.InvertAimingYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming")); + CVarCheckbox("Invert Shield Aiming X Axis", CVAR_SETTING("Controls.InvertShieldAimingXAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Shield Aiming X Axis")); + CVarCheckbox("Invert Shield Aiming Y Axis", CVAR_SETTING("Controls.InvertShieldAimingYAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Shield Aiming Y Axis")); + CVarCheckbox("Invert Z-Weapon Aiming Y Axis", CVAR_SETTING("Controls.InvertZAimingYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-Z-Weapon Aiming")); + CVarCheckbox("Disable Auto-Centering in First-Person View", CVAR_SETTING("DisableFirstPersonAutoCenterView"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming")); + if (CVarCheckbox("Enable Custom Aiming/First-Person sensitivity", CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), CheckboxOptions().Color(THEME_COLOR))) { if (!CVarGetInteger(CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), 0)) { CVarClear(CVAR_SETTING("FirstPersonCameraSensitivity.X")); CVarClear(CVAR_SETTING("FirstPersonCameraSensitivity.Y")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } if (CVarGetInteger(CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), 0)) { - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Horizontal Sensitivity: %.0f %%", "##FirstPersonSensitivity Horizontal", - CVAR_SETTING("FirstPersonCameraSensitivity.X"), 0.01f, 5.0f, "", 1.0f, true); - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Vertical Sensitivity: %.0f %%", "##FirstPersonSensitivity Vertical", - CVAR_SETTING("FirstPersonCameraSensitivity.Y"), 0.01f, 5.0f, "", 1.0f, true); + CVarSliderFloat("Aiming/First-Person Horizontal Sensitivity: %.0f %%", CVAR_SETTING("FirstPersonCameraSensitivity.X"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Aiming/First-Person Vertical Sensitivity: %.0f %%", CVAR_SETTING("FirstPersonCameraSensitivity.Y"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); } - UIWidgets::Spacer(0); Ship::GuiWindow::EndGroupPanel(0); - UIWidgets::Spacer(0); cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("Third-Person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Free Look", CVAR_SETTING("FreeLook.Enabled")); - UIWidgets::Tooltip("Enables free look camera control\nNote: You must remap C buttons off of the right stick in the " - "controller config menu, and map the camera stick to the right stick."); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera X Axis", CVAR_SETTING("FreeLook.InvertXAxis")); - UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-Free look"); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera Y Axis", CVAR_SETTING("FreeLook.InvertYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Free look"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Horizontal Sensitivity: %.0f %%", "##ThirdPersonSensitivity Horizontal", - CVAR_SETTING("FreeLook.CameraSensitivity.X"), 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Vertical Sensitivity: %.0f %%", "##ThirdPersonSensitivity Vertical", - CVAR_SETTING("FreeLook.CameraSensitivity.Y"), 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Distance: %d", "##CamDist", - CVAR_SETTING("FreeLook.MaxCameraDistance"), 100, 900, "", 185, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Transition Speed: %d", "##CamTranSpeed", - CVAR_SETTING("FreeLook.TransitionSpeed"), 0, 900, "", 25, true, false, true); + CVarCheckbox("Free Look", CVAR_SETTING("FreeLook.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Enables free look camera control\nNote: You must remap C buttons off of the right stick in the " + "controller config menu, and map the camera stick to the right stick.")); + CVarCheckbox("Invert Camera X Axis", CVAR_SETTING("FreeLook.InvertXAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Camera X Axis in:\n-Free look")); + CVarCheckbox("Invert Camera Y Axis", CVAR_SETTING("FreeLook.InvertYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-Free look")); + CVarSliderFloat("Third-Person Horizontal Sensitivity: %.0f %%", CVAR_SETTING("FreeLook.CameraSensitivity.X"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Third-Person Vertical Sensitivity: %.0f %%", CVAR_SETTING("FreeLook.CameraSensitivity.Y"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderInt("Camera Distance: %d", CVAR_SETTING("FreeLook.MaxCameraDistance"), IntSliderOptions().Color(THEME_COLOR).Min(100).Max(900).DefaultValue(185).ShowButtons(true)); + CVarSliderInt("Camera Transition Speed: %d", CVAR_SETTING("FreeLook.TransitionSpeed"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(900).DefaultValue(25).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); } @@ -1405,17 +1401,17 @@ void SohInputEditorWindow::DrawDpadControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("D-Pad Options", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support on Pause Screen", CVAR_SETTING("DPadOnPause")); - UIWidgets::Tooltip("Navigate Pause with the D-pad\nIf used with \"D-pad as Equip Items\", you must hold C-Up to equip instead of navigate"); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support in Text Boxes", CVAR_SETTING("DpadInText")); - UIWidgets::Tooltip("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad"); + CVarCheckbox("D-pad Support on Pause Screen", CVAR_SETTING("DPadOnPause"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Navigate Pause with the D-pad\nIf used with \"D-pad as Equip Items\", you must hold C-Up to equip instead of navigate")); + CVarCheckbox("D-pad Support in Text Boxes", CVAR_SETTING("DpadInText"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad")); if (!CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0) && !CVarGetInteger(CVAR_SETTING("DpadInText"), 0)) { ImGui::BeginDisabled(); } - UIWidgets::PaddedEnhancementCheckbox("D-pad hold change", CVAR_SETTING("DpadHoldChange"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("The cursor will only move a single space no matter how long a D-pad direction is held"); + CVarCheckbox("D-pad hold change", CVAR_SETTING("DpadHoldChange"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("The cursor will only move a single space no matter how long a D-pad direction is held")); if (!CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0) && !CVarGetInteger(CVAR_SETTING("DpadInText"), 0)) { ImGui::EndDisabled(); @@ -1536,30 +1532,24 @@ void SohInputEditorWindow::DrawLinkTab() { DrawButtonLine("M2", portIndex, BTN_CUSTOM_MODIFIER2); ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - UIWidgets::PaddedEnhancementCheckbox("Enable speed modifiers", CVAR_SETTING("WalkModifier.Enabled"), true, false); - UIWidgets::Tooltip("Hold the assigned button to change the maximum walking or swimming speed"); + CVarCheckbox("Enable speed modifiers", CVAR_SETTING("WalkModifier.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Hold the assigned button to change the maximum walking or swimming speed")); if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0)) { UIWidgets::Spacer(5); Ship::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Toggle modifier instead of holding", - CVAR_SETTING("WalkModifier.SpeedToggle"), true, false); + CVarCheckbox("Toggle modifier instead of holding", CVAR_SETTING("WalkModifier.SpeedToggle"), CheckboxOptions().Color(THEME_COLOR)); Ship::GuiWindow::BeginGroupPanel("Walk Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Don't affect jump distance/velocity", - CVAR_SETTING("WalkModifier.DoesntChangeJump"), true, false); - UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 1: %.0f %%", "##WalkMod1", - CVAR_SETTING("WalkModifier.Mapping1"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 2: %.0f %%", "##WalkMod2", - CVAR_SETTING("WalkModifier.Mapping2"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); + CVarCheckbox("Don't affect jump distance/velocity", CVAR_SETTING("WalkModifier.DoesntChangeJump"), CheckboxOptions().Color(THEME_COLOR)); + CVarSliderFloat("Walk Modifier 1: %.0f %%", CVAR_SETTING("WalkModifier.Mapping1"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Walk Modifier 2: %.0f %%", CVAR_SETTING("WalkModifier.Mapping2"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); Ship::GuiWindow::BeginGroupPanel("Swim Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 1: %.0f %%", "##SwimMod1", - CVAR_SETTING("WalkModifier.SwimMapping1"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 2: %.0f %%", "##SwimMod2", - CVAR_SETTING("WalkModifier.SwimMapping2"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); + CVarSliderFloat("Swim Modifier 1: %.0f %%", CVAR_SETTING("WalkModifier.SwimMapping1"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Swim Modifier 2: %.0f %%", CVAR_SETTING("WalkModifier.SwimMapping2"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); Ship::GuiWindow::EndGroupPanel(0); } @@ -1659,9 +1649,7 @@ void SohInputEditorWindow::DrawDebugPortTab(uint8_t portIndex, std::string custo UpdateBitmaskToMappingIds(portIndex); UpdateStickDirectionToMappingIds(portIndex); - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader("Buttons", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { DrawButtonLine("A", portIndex, BTN_A, CHIP_COLOR_N64_BLUE); @@ -1690,19 +1678,20 @@ void SohInputEditorWindow::DrawDebugPortTab(uint8_t portIndex, std::string custo DrawStickSection(portIndex, Ship::LEFT, 0); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleHeader(); ImGui::EndTabItem(); } } void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { + PushStyleButton(THEME_COLOR); if (ImGui::Button("Clear All", ImGui::CalcTextSize("Clear All") * 2)) { ImGui::OpenPopup("Clear All##clearAllPopup"); } + PopStyleButton(); if (ImGui::BeginPopupModal("Clear All##clearAllPopup", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all mappings for port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } @@ -1710,6 +1699,7 @@ void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->ClearAllMappings(); ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } } @@ -1717,22 +1707,23 @@ void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { ImGui::SameLine(); auto popupId = StringHelper::Sprintf("setDefaultsPopup##%d", portIndex); + PushStyleButton(THEME_COLOR); if (ImGui::Button(StringHelper::Sprintf("Set Defaults##%d", portIndex).c_str(), ImVec2(ImGui::CalcTextSize("Set Defaults") * 2))) { ImGui::OpenPopup(popupId.c_str()); } + PopStyleButton(); if (ImGui::BeginPopup(popupId.c_str())) { bool shouldClose = false; - ImGui::PushStyleColor(ImGuiCol_Button, BUTTON_COLOR_KEYBOARD_BEIGE); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, BUTTON_COLOR_KEYBOARD_BEIGE_HOVERED); + PushStyleButton(BUTTON_COLOR_KEYBOARD_BEIGE); if (ImGui::Button(StringHelper::Sprintf("%s Keyboard", ICON_FA_KEYBOARD_O).c_str())) { ImGui::OpenPopup("Set Defaults for Keyboard"); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleButton(); if (ImGui::BeginPopupModal("Set Defaults for Keyboard", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all existing mappings for\nKeyboard on port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { shouldClose = true; ImGui::CloseCurrentPopup(); @@ -1747,21 +1738,21 @@ void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { shouldClose = true; ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); GetButtonColorsForDeviceType(Ship::PhysicalDeviceType::SDLGamepad, buttonColor, buttonHoveredColor); - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + PushStyleButton(buttonColor); if (ImGui::Button(StringHelper::Sprintf("%s %s", ICON_FA_GAMEPAD, "Gamepad (SDL)").c_str())) { ImGui::OpenPopup("Set Defaults for Gamepad (SDL)"); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleButton(); if (ImGui::BeginPopupModal("Set Defaults for Gamepad (SDL)", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all existing mappings for\nGamepad (SDL) on port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { shouldClose = true; ImGui::CloseCurrentPopup(); @@ -1776,12 +1767,15 @@ void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { shouldClose = true; ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel") || shouldClose) { ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 16fb2bed4..93c866eb3 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -11,6 +11,7 @@ #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -92,6 +93,7 @@ std::map groupLabels = { typedef struct { const char* cvar; + const char* valuesCvar; const char* rainbowCvar; const char* lockedCvar; const char* changedCvar; @@ -111,7 +113,8 @@ Color_RGBA8 ColorRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { #define COSMETIC_OPTION(id, label, group, defaultColor, supportsAlpha, supportsRainbow, advancedOption) \ { id, { \ - CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), CVAR_COSMETIC(id ".Changed"), label, group, \ + CVAR_COSMETIC(id), CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), \ + CVAR_COSMETIC(id ".Changed"), label, group, \ ImVec4(defaultColor.r / 255.0f, defaultColor.g / 255.0f, defaultColor.b / 255.0f, defaultColor.a / 255.0f), defaultColor, \ supportsAlpha, supportsRainbow, advancedOption \ } } @@ -313,7 +316,7 @@ static std::map cosmeticOptions = { #define MESSAGE_COSMETIC_OPTION(id, label, r, g, b) COSMETIC_OPTION("Message." id, label, COSMETICS_GROUP_MESSAGE, ColorRGBA8(r, g, b, 255), false, true, true) MESSAGE_COSMETIC_OPTION("Default.Normal", "Message Default Color", 255, 255, 255), - MESSAGE_COSMETIC_OPTION("Default.NoneNoShadow", "Message Default (None No Shadow) Color", 0, 0, 0), + MESSAGE_COSMETIC_OPTION("Default.NoneNoShadow", "Message Default (None No Shadow)", 0, 0, 0), MESSAGE_COSMETIC_OPTION("Red.Normal", "Message Red Color", 255, 60, 60), MESSAGE_COSMETIC_OPTION("Red.Wooden", "Message Red (Wooden) Color", 255, 120, 0), MESSAGE_COSMETIC_OPTION("Adjustable.Normal", "Message Adjustable Color", 70, 255, 80), @@ -322,7 +325,7 @@ static std::map cosmeticOptions = { MESSAGE_COSMETIC_OPTION("Blue.Wooden", "Message Blue (Wooden) Color", 80, 110, 255), MESSAGE_COSMETIC_OPTION("LightBlue.Normal", "Message Light Blue Color", 100, 180, 255), MESSAGE_COSMETIC_OPTION("LightBlue.Wooden", "Message Light Blue (Wooden) Color", 90, 180, 255), - MESSAGE_COSMETIC_OPTION("LightBlue.LightBlue.NoneNoShadow", "Message Light Blue (None No Shadow) Color", 80, 150, 180), + MESSAGE_COSMETIC_OPTION("LightBlue.LightBlue.NoneNoShadow", "Message Light Blue (None No Shadow)", 80, 150, 180), MESSAGE_COSMETIC_OPTION("Purple.Normal", "Message Purple Color", 255, 150, 180), MESSAGE_COSMETIC_OPTION("Purple.Wooden", "Message Purple (Wooden) Color", 210, 100, 255), MESSAGE_COSMETIC_OPTION("Yellow.Normal", "Message Yellow Color", 255, 255, 50), @@ -469,41 +472,38 @@ static const char* MarginCvarNonAnchor[] { CVAR_COSMETIC("HUD.TitleCard.Boss") }; -ImVec4 GetRandomValue() { -#if !defined(__SWITCH__) && !defined(__WIIU__) - std::random_device rd; - std::mt19937 rng(rd()); -#else - size_t seed = std::hash{}(std::to_string(rand())); - std::mt19937_64 rng(seed); -#endif - std::uniform_int_distribution dist(0, 255 - 1); - - ImVec4 NewColor; - NewColor.x = (float)(dist(rng)) / 255.0f; - NewColor.y = (float)(dist(rng)) / 255.0f; - NewColor.z = (float)(dist(rng)) / 255.0f; - return NewColor; -} - -void SetMarginAll(const char* ButtonName, bool SetActivated) { - if (ImGui::Button(ButtonName)) { - //MarginCvarNonAnchor is an array that list every element that has No anchor by default, because if that the case this function will not touch it with pose type 0. +void SetMarginAll(const char* ButtonName, bool SetActivated, const char* tooltip) { + if (UIWidgets::Button(ButtonName, UIWidgets::ButtonOptions() + .Size(ImVec2(200.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip(tooltip))) { + // MarginCvarNonAnchor is an array that list every element that has No anchor by default, because if that the + // case this function will not touch it with pose type 0. u8 arrayLengthNonMargin = sizeof(MarginCvarNonAnchor) / sizeof(*MarginCvarNonAnchor); for (auto cvarName : MarginCvarList) { std::string cvarPosType = std::string(cvarName).append(".PosType"); std::string cvarNameMargins = std::string(cvarName).append(".UseMargins"); - if (CVarGetInteger(cvarPosType.c_str(),0) <= ANCHOR_RIGHT && SetActivated) { //Our element is not Hidden or Non anchor + if (CVarGetInteger(cvarPosType.c_str(), 0) <= ANCHOR_RIGHT && + SetActivated) { // Our element is not Hidden or Non anchor for (int i = 0; i < arrayLengthNonMargin; i++) { - if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && (CVarGetInteger(cvarPosType.c_str(), 0) == ORIGINAL_LOCATION)) { //Our element is both in original position and do not have anchor by default so we skip it. - CVarSetInteger(cvarNameMargins.c_str(), false); //force set off - } else if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && (CVarGetInteger(cvarPosType.c_str(), 0) != ORIGINAL_LOCATION)) { //Our element is not in original position regarless it has no anchor by default since player made it anchored we can toggle margins + if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && + (CVarGetInteger(cvarPosType.c_str(), 0) == + ORIGINAL_LOCATION)) { // Our element is both in original position and do not have anchor by + // default so we skip it. + CVarSetInteger(cvarNameMargins.c_str(), false); // force set off + } else if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && + (CVarGetInteger(cvarPosType.c_str(), 0) != + ORIGINAL_LOCATION)) { // Our element is not in original position regarless it has no + // anchor by default since player made it anchored we can toggle + // margins CVarSetInteger(cvarNameMargins.c_str(), SetActivated); - } else if (strcmp(cvarName, MarginCvarNonAnchor[i]) != 0) { //Our elements has an anchor by default so regarless of it's position right now that okay to toggle margins. + } else if (strcmp(cvarName, MarginCvarNonAnchor[i]) != + 0) { // Our elements has an anchor by default so regarless of it's position right now + // that okay to toggle margins. CVarSetInteger(cvarNameMargins.c_str(), SetActivated); } } - } else { //Since the user requested to turn all margin off no need to do any check there. + } else { // Since the user requested to turn all margin off no need to do any check there. CVarSetInteger(cvarNameMargins.c_str(), SetActivated); } } @@ -511,12 +511,16 @@ void SetMarginAll(const char* ButtonName, bool SetActivated) { } void ResetPositionAll() { - if (ImGui::Button("Reset all positions")) { + if (UIWidgets::Button("Reset all positions", + UIWidgets::ButtonOptions() + .Size(ImVec2(200.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Revert every element to use their original position and no margins"))) { for (auto cvarName : MarginCvarList) { std::string cvarPosType = std::string(cvarName).append(".PosType"); std::string cvarNameMargins = std::string(cvarName).append(".UseMargins"); CVarSetInteger(cvarPosType.c_str(), 0); - CVarSetInteger(cvarNameMargins.c_str(), false); //Turn margin off to everythings as that original position. + CVarSetInteger(cvarNameMargins.c_str(), false); // Turn margin off to everythings as that original position. } } } @@ -545,7 +549,7 @@ void CosmeticsUpdateTick() { cosmeticOption.currentColor.z = newColor.b / 255.0f; cosmeticOption.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOption.cvar, newColor); + CVarSetColor(cosmeticOption.valuesCvar, newColor); } // If we don't want the rainbow color on items to be synced, offset the index for each item in the loop. // Technically this would work if you replaced "60" with 1 but the hue would be so close it's @@ -573,21 +577,21 @@ void CosmeticsUpdateTick() { void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& magicFaroresPrimary = cosmeticOptions.at("Magic.FaroresPrimary"); if (manualChange || CVarGetInteger(magicFaroresPrimary.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(magicFaroresPrimary.cvar, magicFaroresPrimary.defaultColor); + Color_RGBA8 color = CVarGetColor(magicFaroresPrimary.valuesCvar, magicFaroresPrimary.defaultColor); PATCH_GFX(sInnerCylinderDL, "Magic_FaroresPrimary1", magicFaroresPrimary.changedCvar, 24, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(sOuterCylinderDL, "Magic_FaroresPrimary2", magicFaroresPrimary.changedCvar, 24, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& magicFaroresSecondary = cosmeticOptions.at("Magic.FaroresSecondary"); if (manualChange || CVarGetInteger(magicFaroresSecondary.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(magicFaroresSecondary.cvar, magicFaroresSecondary.defaultColor); + Color_RGBA8 color = CVarGetColor(magicFaroresSecondary.valuesCvar, magicFaroresSecondary.defaultColor); PATCH_GFX(sInnerCylinderDL, "Magic_FaroresSecondary1", magicFaroresSecondary.changedCvar, 25, gsDPSetEnvColor(color.r, color.g, color.b, 255)); PATCH_GFX(sOuterCylinderDL, "Magic_FaroresSecondary2", magicFaroresSecondary.changedCvar, 25, gsDPSetEnvColor(color.r, color.g, color.b, 255)); } static CosmeticOption& linkGoronTunic = cosmeticOptions.at("Link.GoronTunic"); if (manualChange || CVarGetInteger(linkGoronTunic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkGoronTunic.cvar, linkGoronTunic.defaultColor); + Color_RGBA8 color = CVarGetColor(linkGoronTunic.valuesCvar, linkGoronTunic.defaultColor); PATCH_GFX(gGiGoronTunicColorDL, "Link_GoronTunic1", linkGoronTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoronCollarColorDL, "Link_GoronTunic2", linkGoronTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiGoronTunicColorDL, "Link_GoronTunic3", linkGoronTunic.changedCvar, 4, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); @@ -596,7 +600,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkZoraTunic = cosmeticOptions.at("Link.ZoraTunic"); if (manualChange || CVarGetInteger(linkZoraTunic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkZoraTunic.cvar, linkZoraTunic.defaultColor); + Color_RGBA8 color = CVarGetColor(linkZoraTunic.valuesCvar, linkZoraTunic.defaultColor); PATCH_GFX(gGiZoraTunicColorDL, "Link_ZoraTunic1", linkZoraTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiZoraCollarColorDL, "Link_ZoraTunic2", linkZoraTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiZoraTunicColorDL, "Link_ZoraTunic3", linkZoraTunic.changedCvar, 4, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); @@ -605,7 +609,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkHair = cosmeticOptions.at("Link.Hair"); if (manualChange || CVarGetInteger(linkHair.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkHair.cvar, linkHair.defaultColor); + Color_RGBA8 color = CVarGetColor(linkHair.valuesCvar, linkHair.defaultColor); PATCH_GFX(gLinkChildHeadNearDL, "Link_Hair1", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildHeadFarDL, "Link_Hair2", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultHeadNearDL, "Link_Hair3", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -627,7 +631,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkLinen = cosmeticOptions.at("Link.Linen"); if (manualChange || CVarGetInteger(linkLinen.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkLinen.cvar, linkLinen.defaultColor); + Color_RGBA8 color = CVarGetColor(linkLinen.valuesCvar, linkLinen.defaultColor); PATCH_GFX(gLinkAdultLeftArmNearDL, "Link_Linen1", linkLinen.changedCvar, 30, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftArmNearDL, "Link_Linen2", linkLinen.changedCvar, 83, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftArmOutNearDL, "Link_Linen3", linkLinen.changedCvar, 25, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -668,7 +672,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkBoots = cosmeticOptions.at("Link.Boots"); if (manualChange || CVarGetInteger(linkBoots.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkBoots.cvar, linkBoots.defaultColor); + Color_RGBA8 color = CVarGetColor(linkBoots.valuesCvar, linkBoots.defaultColor); PATCH_GFX(gLinkChildRightShinNearDL, "Link_Boots1", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildRightShinFarDL, "Link_Boots2", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultRightLegNearDL, "Link_Boots3", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -704,7 +708,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& mirrorShieldBody = cosmeticOptions.at("MirrorShield.Body"); if (manualChange || CVarGetInteger(mirrorShieldBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldBody.cvar, mirrorShieldBody.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldBody.valuesCvar, mirrorShieldBody.defaultColor); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Body1", mirrorShieldBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Body2", mirrorShieldBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Body3", mirrorShieldBody.changedCvar, 28, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -716,7 +720,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& mirrorShieldMirror = cosmeticOptions.at("MirrorShield.Mirror"); if (manualChange || CVarGetInteger(mirrorShieldMirror.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldMirror.cvar, mirrorShieldMirror.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldMirror.valuesCvar, mirrorShieldMirror.defaultColor); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Mirror1", mirrorShieldMirror.changedCvar, 47, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Mirror2", mirrorShieldMirror.changedCvar, 48, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Mirror3", mirrorShieldMirror.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -728,7 +732,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& mirrorShieldEmblem = cosmeticOptions.at("MirrorShield.Emblem"); if (manualChange || CVarGetInteger(mirrorShieldEmblem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldEmblem.cvar, mirrorShieldEmblem.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldEmblem.valuesCvar, mirrorShieldEmblem.defaultColor); PATCH_GFX(gGiMirrorShieldSymbolDL, "MirrorShield_Emblem1", mirrorShieldEmblem.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 140)); PATCH_GFX(gGiMirrorShieldSymbolDL, "MirrorShield_Emblem2", mirrorShieldEmblem.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Emblem3", mirrorShieldEmblem.changedCvar, 165, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -741,7 +745,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& swordsKokiriBlade = cosmeticOptions.at("Swords.KokiriBlade"); if (manualChange || CVarGetInteger(swordsKokiriBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsKokiriBlade.cvar, swordsKokiriBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsKokiriBlade.valuesCvar, swordsKokiriBlade.defaultColor); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordNearDL, "Swords_KokiriBlade1", swordsKokiriBlade.changedCvar, 79, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordFarDL, "Swords_KokiriBlade2", swordsKokiriBlade.changedCvar, 75, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiKokiriSwordDL, "Swords_KokiriBlade3", swordsKokiriBlade.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -750,7 +754,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsKokiriHilt = cosmeticOptions.at("Swords.KokiriHilt"); if (manualChange || CVarGetInteger(swordsKokiriHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsKokiriHilt.cvar, swordsKokiriHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsKokiriHilt.valuesCvar, swordsKokiriHilt.defaultColor); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordNearDL, "Swords_KokiriHilt1", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordFarDL, "Swords_KokiriHilt2", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildSwordAndSheathNearDL, "Swords_KokiriHilt3", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -791,7 +795,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& swordsMasterBlade = cosmeticOptions.at("Swords.MasterBlade"); if (manualChange || CVarGetInteger(swordsMasterBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsMasterBlade.cvar, swordsMasterBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsMasterBlade.valuesCvar, swordsMasterBlade.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordFarDL, "Swords_MasterBlade1", swordsMasterBlade.changedCvar, 60, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordNearDL, "Swords_MasterBlade2", swordsMasterBlade.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(object_toki_objects_DL_001BD0, "Swords_MasterBlade3", swordsMasterBlade.changedCvar, 13, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -802,7 +806,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsMasterHilt = cosmeticOptions.at("Swords.MasterHilt"); if (manualChange || CVarGetInteger(swordsMasterHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsMasterHilt.cvar, swordsMasterHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsMasterHilt.valuesCvar, swordsMasterHilt.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordNearDL, "Swords_MasterHilt1", swordsMasterHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordFarDL, "Swords_MasterHilt2", swordsMasterHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(object_toki_objects_DL_001BD0, "Swords_MasterHilt3", swordsMasterHilt.changedCvar, 16, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -849,7 +853,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& swordsBiggoronBlade = cosmeticOptions.at("Swords.BiggoronBlade"); if (manualChange || CVarGetInteger(swordsBiggoronBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsBiggoronBlade.cvar, swordsBiggoronBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsBiggoronBlade.valuesCvar, swordsBiggoronBlade.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingBgsFarDL, "Swords_BiggoronBlade1", swordsBiggoronBlade.changedCvar, 108, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingBgsNearDL, "Swords_BiggoronBlade2", swordsBiggoronBlade.changedCvar, 63, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBiggoronSwordDL, "Swords_BiggoronBlade3", swordsBiggoronBlade.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -858,7 +862,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsBiggoronHilt = cosmeticOptions.at("Swords.BiggoronHilt"); if (manualChange || CVarGetInteger(swordsBiggoronHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsBiggoronHilt.cvar, swordsBiggoronHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsBiggoronHilt.valuesCvar, swordsBiggoronHilt.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingBgsNearDL, "Swords_BiggoronHilt1", swordsBiggoronHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingBgsFarDL, "Swords_BiggoronHilt2", swordsBiggoronHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGiBiggoronSwordDL, "Swords_BiggoronHilt3", swordsBiggoronHilt.changedCvar, 74, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -877,7 +881,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& glovesGoronBracelet = cosmeticOptions.at("Gloves.GoronBracelet"); if (manualChange || CVarGetInteger(glovesGoronBracelet.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGoronBracelet.cvar, glovesGoronBracelet.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGoronBracelet.valuesCvar, glovesGoronBracelet.defaultColor); PATCH_GFX(gGiGoronBraceletDL, "Gloves_GoronBracelet1", glovesGoronBracelet.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoronBraceletDL, "Gloves_GoronBracelet2", glovesGoronBracelet.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildGoronBraceletDL, "Gloves_GoronBracelet3", glovesGoronBracelet.changedCvar, 3, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -889,19 +893,19 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& glovesSilverGauntlets = cosmeticOptions.at("Gloves.SilverGauntlets"); if (manualChange || CVarGetInteger(glovesSilverGauntlets.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesSilverGauntlets.cvar, glovesSilverGauntlets.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesSilverGauntlets.valuesCvar, glovesSilverGauntlets.defaultColor); PATCH_GFX(gGiSilverGauntletsColorDL, "Gloves_SilverGauntlets1", glovesSilverGauntlets.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiSilverGauntletsColorDL, "Gloves_SilverGauntlets2", glovesSilverGauntlets.changedCvar, 4, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); } static CosmeticOption& glovesGoldenGauntlets = cosmeticOptions.at("Gloves.GoldenGauntlets"); if (manualChange || CVarGetInteger(glovesGoldenGauntlets.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGoldenGauntlets.cvar, glovesGoldenGauntlets.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGoldenGauntlets.valuesCvar, glovesGoldenGauntlets.defaultColor); PATCH_GFX(gGiGoldenGauntletsColorDL, "Gloves_GoldenGauntlets1", glovesGoldenGauntlets.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoldenGauntletsColorDL, "Gloves_GoldenGauntlets2", glovesGoldenGauntlets.changedCvar, 4, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); } static CosmeticOption& glovesGauntletsGem = cosmeticOptions.at("Gloves.GauntletsGem"); if (manualChange || CVarGetInteger(glovesGauntletsGem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGauntletsGem.cvar, glovesGauntletsGem.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGauntletsGem.valuesCvar, glovesGauntletsGem.defaultColor); PATCH_GFX(gGiGauntletsDL, "Gloves_GauntletsGem1", glovesGauntletsGem.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGauntletsDL, "Gloves_GauntletsGem2", glovesGauntletsGem.changedCvar, 85, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultLeftGauntletPlate2DL, "Gloves_GauntletsGem3", glovesGauntletsGem.changedCvar, 42, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -912,7 +916,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBoomerangBody = cosmeticOptions.at("Equipment.BoomerangBody"); if (manualChange || CVarGetInteger(equipmentBoomerangBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBoomerangBody.cvar, equipmentBoomerangBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBoomerangBody.valuesCvar, equipmentBoomerangBody.defaultColor); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangBody1", equipmentBoomerangBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangBody2", equipmentBoomerangBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildLeftFistAndBoomerangNearDL, "Equipment_BoomerangBody3", equipmentBoomerangBody.changedCvar, 34, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -921,7 +925,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBoomerangGem = cosmeticOptions.at("Equipment.BoomerangGem"); if (manualChange || CVarGetInteger(equipmentBoomerangGem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBoomerangGem.cvar, equipmentBoomerangGem.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBoomerangGem.valuesCvar, equipmentBoomerangGem.defaultColor); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangGem1", equipmentBoomerangGem.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangGem2", equipmentBoomerangGem.changedCvar, 85, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildLeftFistAndBoomerangNearDL, "Equipment_BoomerangGem3", equipmentBoomerangGem.changedCvar, 16, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -932,7 +936,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& equipmentSlingshotBody = cosmeticOptions.at("Equipment.SlingshotBody"); if (manualChange || CVarGetInteger(equipmentSlingshotBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentSlingshotBody.cvar, equipmentSlingshotBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentSlingshotBody.valuesCvar, equipmentSlingshotBody.defaultColor); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody1", equipmentSlingshotBody.changedCvar, 10, gsDPSetPrimColor(0, 0, MAX(color.r - 100, 0), MAX(color.g - 100, 0), MAX(color.b - 100, 0), 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody2", equipmentSlingshotBody.changedCvar, 12, gsDPSetEnvColor(MAX(color.r - 100, 0) / 3, MAX(color.g - 100, 0) / 3, MAX(color.b - 100, 0) / 3, 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody3", equipmentSlingshotBody.changedCvar, 74, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -956,7 +960,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& equipmentSlingshotString = cosmeticOptions.at("Equipment.SlingshotString"); if (manualChange || CVarGetInteger(equipmentSlingshotString.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentSlingshotString.cvar, equipmentSlingshotString.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentSlingshotString.valuesCvar, equipmentSlingshotString.defaultColor); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotString1",equipmentSlingshotString.changedCvar, 75, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotString2",equipmentSlingshotString.changedCvar, 76, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gLinkChildSlingshotStringDL, "Equipment_SlingshotString3",equipmentSlingshotString.changedCvar, 9, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -964,7 +968,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBowTips = cosmeticOptions.at("Equipment.BowTips"); if (manualChange || CVarGetInteger(equipmentBowTips.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowTips.cvar, equipmentBowTips.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowTips.valuesCvar, equipmentBowTips.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowTips1", equipmentBowTips.changedCvar, 86, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowTips2", equipmentBowTips.changedCvar, 87, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowTips3", equipmentBowTips.changedCvar, 34, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -973,14 +977,14 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBowString = cosmeticOptions.at("Equipment.BowString"); if (manualChange || CVarGetInteger(equipmentBowString.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowString.cvar, equipmentBowString.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowString.valuesCvar, equipmentBowString.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowString1", equipmentBowString.changedCvar, 105, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowString2", equipmentBowString.changedCvar, 106, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultBowStringDL, "Equipment_BowString3", equipmentBowString.changedCvar, 9, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& equipmentBowBody = cosmeticOptions.at("Equipment.BowBody"); if (manualChange || CVarGetInteger(equipmentBowBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowBody.cvar, equipmentBowBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowBody.valuesCvar, equipmentBowBody.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowBody1", equipmentBowBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowBody2", equipmentBowBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowBody3", equipmentBowBody.changedCvar, 42, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -989,7 +993,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBowHandle = cosmeticOptions.at("Equipment.BowHandle"); if (manualChange || CVarGetInteger(equipmentBowHandle.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowHandle.cvar, equipmentBowHandle.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowHandle.valuesCvar, equipmentBowHandle.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowHandle1", equipmentBowHandle.changedCvar, 51, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowHandle2", equipmentBowHandle.changedCvar, 52, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowHandle3", equipmentBowHandle.changedCvar, 18, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -999,7 +1003,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentHammerHead = cosmeticOptions.at("Equipment.HammerHead"); if (manualChange || CVarGetInteger(equipmentHammerHead.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHammerHead.cvar, equipmentHammerHead.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHammerHead.valuesCvar, equipmentHammerHead.defaultColor); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead1", equipmentHammerHead.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead2", equipmentHammerHead.changedCvar, 6, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead3", equipmentHammerHead.changedCvar, 68, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1009,7 +1013,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentHammerHandle = cosmeticOptions.at("Equipment.HammerHandle"); if (manualChange || CVarGetInteger(equipmentHammerHandle.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHammerHandle.cvar, equipmentHammerHandle.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHammerHandle.valuesCvar, equipmentHammerHandle.defaultColor); PATCH_GFX(gGiHammerDL, "Equipment_HammerHandle1", equipmentHammerHandle.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHandle2", equipmentHammerHandle.changedCvar, 85, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingHammerNearDL, "Equipment_HammerHandle5", equipmentHammerHandle.changedCvar, 18, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1018,13 +1022,13 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentHookshotChain = cosmeticOptions.at("Equipment.HookshotChain"); if (manualChange || CVarGetInteger(equipmentHookshotChain.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHookshotChain.cvar, equipmentHookshotChain.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHookshotChain.valuesCvar, equipmentHookshotChain.defaultColor); PATCH_GFX(gLinkAdultHookshotChainDL, "Equipment_HookshotChain1", equipmentHookshotChain.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& equipmentChuFace = cosmeticOptions.at("Equipment.ChuFace"); if (manualChange || CVarGetInteger(equipmentChuFace.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentChuFace.cvar, equipmentChuFace.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentChuFace.valuesCvar, equipmentChuFace.defaultColor); PATCH_GFX(gGiBombchuDL, "Equipment_ChuFace1", equipmentChuFace.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuFace2", equipmentChuFace.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gBombchuDL, "Equipment_ChuFace3", equipmentChuFace.changedCvar, 2, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -1036,7 +1040,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentChuBody = cosmeticOptions.at("Equipment.ChuBody"); if (manualChange || CVarGetInteger(equipmentChuBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentChuBody.cvar, equipmentChuBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentChuBody.valuesCvar, equipmentChuBody.defaultColor); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody1", equipmentChuBody.changedCvar, 39, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody2", equipmentChuBody.changedCvar, 40, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody3", equipmentChuBody.changedCvar, 60, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1046,7 +1050,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBunnyHood = cosmeticOptions.at("Equipment.BunnyHood"); if (manualChange || CVarGetInteger(equipmentBunnyHood.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBunnyHood.cvar, equipmentBunnyHood.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBunnyHood.valuesCvar, equipmentBunnyHood.defaultColor); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood1", equipmentBunnyHood.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood2", equipmentBunnyHood.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood3", equipmentBunnyHood.changedCvar, 83, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1065,7 +1069,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& consumableGreenRupee = cosmeticOptions.at("Consumable.GreenRupee"); if (manualChange || CVarGetInteger(consumableGreenRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableGreenRupee.cvar, consumableGreenRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableGreenRupee.valuesCvar, consumableGreenRupee.defaultColor); PATCH_GFX(gGiGreenRupeeInnerColorDL, "Consumable_GreenRupee1", consumableGreenRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGreenRupeeInnerColorDL, "Consumable_GreenRupee2", consumableGreenRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiGreenRupeeOuterColorDL, "Consumable_GreenRupee3", consumableGreenRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1084,7 +1088,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableBlueRupee = cosmeticOptions.at("Consumable.BlueRupee"); if (manualChange || CVarGetInteger(consumableBlueRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableBlueRupee.cvar, consumableBlueRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableBlueRupee.valuesCvar, consumableBlueRupee.defaultColor); PATCH_GFX(gGiBlueRupeeInnerColorDL, "Consumable_BlueRupee1", consumableBlueRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBlueRupeeInnerColorDL, "Consumable_BlueRupee2", consumableBlueRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiBlueRupeeOuterColorDL, "Consumable_BlueRupee3", consumableBlueRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1092,7 +1096,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableRedRupee = cosmeticOptions.at("Consumable.RedRupee"); if (manualChange || CVarGetInteger(consumableRedRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableRedRupee.cvar, consumableRedRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableRedRupee.valuesCvar, consumableRedRupee.defaultColor); PATCH_GFX(gGiRedRupeeInnerColorDL, "Consumable_RedRupee1", consumableRedRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiRedRupeeInnerColorDL, "Consumable_RedRupee2", consumableRedRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiRedRupeeOuterColorDL, "Consumable_RedRupee3", consumableRedRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1100,7 +1104,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumablePurpleRupee = cosmeticOptions.at("Consumable.PurpleRupee"); if (manualChange || CVarGetInteger(consumablePurpleRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumablePurpleRupee.cvar, consumablePurpleRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumablePurpleRupee.valuesCvar, consumablePurpleRupee.defaultColor); PATCH_GFX(gGiPurpleRupeeInnerColorDL, "Consumable_PurpleRupee1", consumablePurpleRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiPurpleRupeeInnerColorDL, "Consumable_PurpleRupee2", consumablePurpleRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiPurpleRupeeOuterColorDL, "Consumable_PurpleRupee3", consumablePurpleRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1108,7 +1112,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableGoldRupee = cosmeticOptions.at("Consumable.GoldRupee"); if (manualChange || CVarGetInteger(consumableGoldRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableGoldRupee.cvar, consumableGoldRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableGoldRupee.valuesCvar, consumableGoldRupee.defaultColor); PATCH_GFX(gGiGoldRupeeInnerColorDL, "Consumable_GoldRupee1", consumableGoldRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoldRupeeInnerColorDL, "Consumable_GoldRupee2", consumableGoldRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiGoldRupeeOuterColorDL, "Consumable_GoldRupee3", consumableGoldRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1117,7 +1121,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& consumableHearts = cosmeticOptions.at("Consumable.Hearts"); if (manualChange || CVarGetInteger(consumableHearts.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableHearts.cvar, consumableHearts.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableHearts.valuesCvar, consumableHearts.defaultColor); /* PATCH_GFX(gGiRecoveryHeartDL, "Consumable_Hearts1", consumableHearts.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGiRecoveryHeartDL, "Consumable_Hearts2", consumableHearts.changedCvar, 26, gsSPGrayscale(true)); @@ -1133,7 +1137,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableMagic = cosmeticOptions.at("Consumable.Magic"); if (manualChange || CVarGetInteger(consumableMagic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableMagic.cvar, consumableMagic.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableMagic.valuesCvar, consumableMagic.defaultColor); PATCH_GFX(gGiMagicJarSmallDL, "Consumable_Magic1", consumableMagic.changedCvar, 31, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMagicJarSmallDL, "Consumable_Magic2", consumableMagic.changedCvar, 32, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiMagicJarLargeDL, "Consumable_Magic3", consumableMagic.changedCvar, 31, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1144,7 +1148,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcGoldenSkulltula = cosmeticOptions.at("NPC.GoldenSkulltula"); if (manualChange || CVarGetInteger(npcGoldenSkulltula.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcGoldenSkulltula.cvar, npcGoldenSkulltula.defaultColor); + Color_RGBA8 color = CVarGetColor(npcGoldenSkulltula.valuesCvar, npcGoldenSkulltula.defaultColor); PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula1", npcGoldenSkulltula.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula2", npcGoldenSkulltula.changedCvar, 6, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gSkulltulaTokenFlameDL, "NPC_GoldenSkulltula3", npcGoldenSkulltula.changedCvar, 32, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1159,7 +1163,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcGerudo = cosmeticOptions.at("NPC.Gerudo"); if (manualChange || CVarGetInteger(npcGerudo.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcGerudo.cvar, npcGerudo.defaultColor); + Color_RGBA8 color = CVarGetColor(npcGerudo.valuesCvar, npcGerudo.defaultColor); PATCH_GFX(gGerudoPurpleTorsoDL, "NPC_Gerudo1", npcGerudo.changedCvar, 139, gsDPSetEnvColor( color.r, color.g, color.b, 255)); PATCH_GFX(gGerudoPurpleRightThighDL, "NPC_Gerudo2", npcGerudo.changedCvar, 11, gsDPSetEnvColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGerudoPurpleLeftThighDL, "NPC_Gerudo3", npcGerudo.changedCvar, 11, gsDPSetEnvColor(color.r, color.g, color.b, 255)); @@ -1172,39 +1176,39 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcMetalTrap = cosmeticOptions.at("NPC.MetalTrap"); if (manualChange || CVarGetInteger(npcMetalTrap.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcMetalTrap.cvar, npcMetalTrap.defaultColor); + Color_RGBA8 color = CVarGetColor(npcMetalTrap.valuesCvar, npcMetalTrap.defaultColor); PATCH_GFX(gSlidingBladeTrapDL, "NPC_MetalTrap1", npcMetalTrap.changedCvar, 59, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& n64LogoRed = cosmeticOptions.at("Title.N64LogoRed"); if (manualChange || CVarGetInteger(n64LogoRed.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoRed.cvar, n64LogoRed.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoRed.valuesCvar, n64LogoRed.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoRed1", n64LogoRed.changedCvar, 17, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoRed2", n64LogoRed.changedCvar, 18, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoBlue = cosmeticOptions.at("Title.N64LogoBlue"); if (manualChange || CVarGetInteger(n64LogoBlue.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoBlue.cvar, n64LogoBlue.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoBlue.valuesCvar, n64LogoBlue.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoBlue1", n64LogoBlue.changedCvar, 29, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoBlue2", n64LogoBlue.changedCvar, 30, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoGreen = cosmeticOptions.at("Title.N64LogoGreen"); if (manualChange || CVarGetInteger(n64LogoGreen.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoGreen.cvar, n64LogoGreen.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoGreen.valuesCvar, n64LogoGreen.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoGreen1", n64LogoGreen.changedCvar, 56, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoGreen2", n64LogoGreen.changedCvar, 57, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoYellow = cosmeticOptions.at("Title.N64LogoYellow"); if (manualChange || CVarGetInteger(n64LogoYellow.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoYellow.cvar, n64LogoYellow.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoYellow.valuesCvar, n64LogoYellow.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoYellow1", n64LogoYellow.changedCvar, 81, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoYellow2", n64LogoYellow.changedCvar, 82, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } if (gPlayState != nullptr) { - if (CVarGetInteger(CVAR_COSMETIC("Link.BodyScale.Changed"), 0)) { + if (CVarGetInteger(CVAR_COSMETIC("Link.BodySize.Changed"), 0)) { static Player* player = GET_PLAYER(gPlayState); - float scale = CVarGetFloat(CVAR_COSMETIC("Link.BodyScale.Value"), 0.01f); + float scale = CVarGetFloat(CVAR_COSMETIC("Link.BodySize.Value"), 0.01f); player->actor.scale.x = scale; player->actor.scale.y = scale; player->actor.scale.z = scale; @@ -1235,24 +1239,36 @@ void Table_InitHeader(bool has_header = true) { void DrawUseMarginsSlider(const std::string ElementName, const std::string CvarName){ std::string CvarLabel = CvarName + ".UseMargins"; std::string Label = ElementName + " use margins"; - UIWidgets::EnhancementCheckbox(Label.c_str(), CvarLabel.c_str()); - UIWidgets::Tooltip("Using this allow you move the element with General margins sliders"); + UIWidgets::CVarCheckbox(Label.c_str(), CvarLabel.c_str(), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Using this allow you move the element with General margins sliders")); } -void DrawPositionsRadioBoxes(const std::string CvarName, bool NoAnchorEnabled = true){ +void DrawPositionsRadioBoxes(const std::string CvarName, bool NoAnchorEnabled = true) { std::string CvarLabel = CvarName + ".PosType"; - UIWidgets::EnhancementRadioButton("Original position", CvarLabel.c_str(), 0); - UIWidgets::Tooltip("This will use original intended elements position"); - UIWidgets::EnhancementRadioButton("Anchor to the left", CvarLabel.c_str(), 1); - UIWidgets::Tooltip("This will make your elements follow the left side of your game window"); - UIWidgets::EnhancementRadioButton("Anchor to the right", CvarLabel.c_str(), 2); - UIWidgets::Tooltip("This will make your elements follow the right side of your game window"); + UIWidgets::CVarRadioButton("Original position", CvarLabel.c_str(), 0, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will use original intended elements position")); + UIWidgets::CVarRadioButton("Anchor to the left", CvarLabel.c_str(), 1, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the left side of your game window")); + UIWidgets::CVarRadioButton("Anchor to the right", CvarLabel.c_str(), 2, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the right side of your game window")); if (NoAnchorEnabled) { - UIWidgets::EnhancementRadioButton("No anchors", CvarLabel.c_str(), 3); - UIWidgets::Tooltip("This will make your elements to not follow any side\nBetter used for center elements"); + UIWidgets::CVarRadioButton( + "No anchors", CvarLabel.c_str(), 3, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements to not follow any side\nBetter used for center elements")); } - UIWidgets::EnhancementRadioButton("Hidden", CvarLabel.c_str(), 4); - UIWidgets::Tooltip("This will make your elements hidden"); + UIWidgets::CVarRadioButton( + "Hidden", CvarLabel.c_str(), 4, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("This will make your elements hidden")); } void DrawPositionSlider(const std::string CvarName, int MinY, int MaxY, int MinX, int MaxX){ @@ -1260,10 +1276,22 @@ void DrawPositionSlider(const std::string CvarName, int MinY, int MaxY, int MinX std::string PosYCvar = CvarName + ".PosY"; std::string InvisibleLabelX = "##" + PosXCvar; std::string InvisibleLabelY = "##" + PosYCvar; - UIWidgets::EnhancementSliderInt("Up <-> Down : %d", InvisibleLabelY.c_str(), PosYCvar.c_str(), MinY, MaxY, "", 0); - UIWidgets::Tooltip("This slider is used to move Up and Down your elements."); - UIWidgets::EnhancementSliderInt("Left <-> Right : %d", InvisibleLabelX.c_str(), PosXCvar.c_str(), MinX, MaxX, "", 0); - UIWidgets::Tooltip("This slider is used to move Left and Right your elements."); + UIWidgets::CVarSliderInt("Up <-> Down : %d", PosYCvar.c_str(), + UIWidgets::IntSliderOptions() + .Min(MinY) + .Max(MaxY) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This slider is used to move Up and Down your elements.")); + UIWidgets::CVarSliderInt("Left <-> Right : %d", PosXCvar.c_str(), + UIWidgets::IntSliderOptions() + .Min(MinX) + .Max(MaxX) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This slider is used to move Left and Right your elements.")); } void DrawScaleSlider(const std::string CvarName, float DefaultValue){ @@ -1274,6 +1302,7 @@ void DrawScaleSlider(const std::string CvarName, float DefaultValue){ } void Draw_Table_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, int MinY, int MaxY, int MinX, int MaxX, float Default_Value) { + UIWidgets::PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader(Header_Title)) { if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); @@ -1282,13 +1311,14 @@ void Draw_Table_Dropdown(const char* Header_Title, const char* Table_ID, const c DrawPositionsRadioBoxes(Slider_ID); DrawPositionSlider(Slider_ID, MinY, MaxY, MinX, MaxX); DrawScaleSlider(Slider_ID, Default_Value); - ImGui::NewLine(); ImGui::EndTable(); } } + UIWidgets::PopStyleHeader(); } void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, const char* Int_Type, float Slider_Scale_Value) { + UIWidgets::PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader(Header_Title)) { if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); @@ -1306,7 +1336,6 @@ void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const cha } DrawPositionSlider(Slider_ID, 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_CU, Max_X_CU); DrawScaleSlider(Slider_ID, Slider_Scale_Value); - ImGui::NewLine(); ImGui::EndTable(); } std::shared_ptr controller = Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0); @@ -1323,27 +1352,48 @@ void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const cha controller->GetButton(BTN_CUSTOM_OCARINA_NOTE_D5)->AddButtonMapping(mapping); } } + UIWidgets::PopStyleHeader(); } void Draw_Placements(){ - if (ImGui::BeginTable("tableMargins", 1, FlagsTable)) { - ImGui::TableSetupColumn("General margins settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(); - UIWidgets::EnhancementSliderInt("Top : %dx", "##UIMARGINT", CVAR_COSMETIC("HUD.Margin.T"), static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1, 25, "", 0); - UIWidgets::EnhancementSliderInt("Left: %dx", "##UIMARGINL", CVAR_COSMETIC("HUD.Margin.L"), -25, static_cast(ImGui::GetWindowViewport()->Size.x), "", 0); - UIWidgets::EnhancementSliderInt("Right: %dx", "##UIMARGINR", CVAR_COSMETIC("HUD.Margin.R"), static_cast(ImGui::GetWindowViewport()->Size.x) * -1, 25, "", 0); - UIWidgets::EnhancementSliderInt("Bottom: %dx", "##UIMARGINB", CVAR_COSMETIC("HUD.Margin.B"), static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1, 25, "", 0); - SetMarginAll("All margins on",true); - UIWidgets::Tooltip("Set most of the elements to use margins\nSome elements with default position will not be affected\nElements without Anchor or Hidden will not be turned on"); - ImGui::SameLine(); - SetMarginAll("All margins off",false); - UIWidgets::Tooltip("Set all of the elements to not use margins"); - ImGui::SameLine(); - ResetPositionAll(); - UIWidgets::Tooltip("Revert every element to use their original position and no margins"); - ImGui::NewLine(); - ImGui::EndTable(); - } + UIWidgets::PushStyleHeader(THEME_COLOR); + ImGui::SeparatorText("General Margins Settings"); + UIWidgets::CVarSliderInt("Top: %dpx", CVAR_COSMETIC("HUD.Margin.T"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Left: %dpx", CVAR_COSMETIC("HUD.Margin.L"), + UIWidgets::IntSliderOptions() + .Min(-25) + .Max(static_cast(ImGui::GetWindowViewport()->Size.x)) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Right: %dpx", CVAR_COSMETIC("HUD.Margin.R"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.x) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Bottom: %dpx", CVAR_COSMETIC("HUD.Margin.B"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + SetMarginAll("All margins on", true, + "Set most of the elements to use margins\nSome elements with default position will not be " + "affected\nElements without Anchor or Hidden will not be turned on"); + ImGui::SameLine(); + SetMarginAll("All margins off", false, "Set all of the elements to not use margins"); + ImGui::SameLine(); + ResetPositionAll(); + UIWidgets::Separator(true, true, 2.0f, 2.0f); if (ImGui::CollapsingHeader("Hearts count position")) { if (ImGui::BeginTable("tableHeartsCounts", 1, FlagsTable)) { ImGui::TableSetupColumn("Hearts counts settings", FlagsCell, TablesCellsWidth); @@ -1352,9 +1402,14 @@ void Draw_Placements(){ DrawPositionsRadioBoxes(CVAR_COSMETIC("HUD.HeartsCount")); DrawPositionSlider(CVAR_COSMETIC("HUD.HeartsCount"), -22, static_cast(ImGui::GetWindowViewport()->Size.y), -125, static_cast(ImGui::GetWindowViewport()->Size.x)); DrawScaleSlider(CVAR_COSMETIC("HUD.HeartsCount"), 0.7f); - UIWidgets::EnhancementSliderInt("Heart line length : %d", "##HeartLineLength", CVAR_COSMETIC("HUD.Hearts.LineLength"), 0, 20, "", 10); - UIWidgets::Tooltip("This will set the length of a row of hearts. Set to 0 for unlimited length."); - ImGui::NewLine(); + UIWidgets::CVarSliderInt("Heart line length : %d", CVAR_COSMETIC("HUD.Hearts.LineLength"), + UIWidgets::IntSliderOptions() + .Min(0) + .Max(20) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This will set the length of a row of hearts. Set to 0 for unlimited length.")); ImGui::EndTable(); } } @@ -1364,11 +1419,13 @@ void Draw_Placements(){ Table_InitHeader(false); DrawUseMarginsSlider("Magic meter", CVAR_COSMETIC("HUD.MagicBar")); DrawPositionsRadioBoxes(CVAR_COSMETIC("HUD.MagicBar")); - UIWidgets::EnhancementRadioButton("Anchor to life bar", CVAR_COSMETIC("HUD.MagicBar.PosType"), 5); - UIWidgets::Tooltip("This will make your elements follow the bottom of the life meter"); + UIWidgets::CVarRadioButton( + "Anchor to life bar", CVAR_COSMETIC("HUD.MagicBar.PosType"), 5, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the bottom of the life meter")); DrawPositionSlider(CVAR_COSMETIC("HUD.MagicBar"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), -5, static_cast(ImGui::GetWindowViewport()->Size.x / 2)); DrawScaleSlider(CVAR_COSMETIC("HUD.MagicBar"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1387,7 +1444,6 @@ void Draw_Placements(){ } DrawPositionSlider(CVAR_COSMETIC("HUD.VisualSoA"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_VSOA, Max_X_VSOA); DrawScaleSlider(CVAR_COSMETIC("HUD.VisualSoA"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1413,7 +1469,6 @@ void Draw_Placements(){ } DrawPositionSlider(CVAR_COSMETIC("HUD.Dpad"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_Dpad, Max_X_Dpad); DrawScaleSlider(CVAR_COSMETIC("HUD.Dpad"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1440,66 +1495,94 @@ void Draw_Placements(){ ImGui::TableSetupColumn("Enemy Health Bar settings", FlagsCell, TablesCellsWidth); Table_InitHeader(false); std::string posTypeCVar = CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"); - UIWidgets::EnhancementRadioButton("Anchor to Enemy", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_ACTOR); - UIWidgets::Tooltip("This will use enemy on screen position"); - UIWidgets::EnhancementRadioButton("Anchor to the top", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_TOP); - UIWidgets::Tooltip("This will make your elements follow the top edge of your game window"); - UIWidgets::EnhancementRadioButton("Anchor to the bottom", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_BOTTOM); - UIWidgets::Tooltip("This will make your elements follow the bottom edge of your game window"); + UIWidgets::CVarRadioButton("Anchor to Enemy", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), + ENEMYHEALTH_ANCHOR_ACTOR, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will use enemy on screen position")); + UIWidgets::CVarRadioButton( + "Anchor to the top", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), ENEMYHEALTH_ANCHOR_TOP, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the top edge of your game window")); + UIWidgets::CVarRadioButton( + "Anchor to the bottom", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), ENEMYHEALTH_ANCHOR_BOTTOM, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the bottom edge of your game window")); DrawPositionSlider(CVAR_COSMETIC("HUD.EnemyHealthBar."), -SCREEN_HEIGHT, SCREEN_HEIGHT, -static_cast(ImGui::GetWindowViewport()->Size.x / 2), static_cast(ImGui::GetWindowViewport()->Size.x / 2)); - if (UIWidgets::EnhancementSliderInt("Health Bar Width: %d", "##EnemyHealthBarWidth", CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value"), 32, 128, "", 64)) { + if (UIWidgets::CVarSliderInt( + "Health Bar Width: %d", CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value"), + UIWidgets::IntSliderOptions() + .Min(32) + .Max(128) + .DefaultValue(64) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This will change the width of the health bar"))) { CVarSetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Changed"), 1); } - UIWidgets::Tooltip("This will change the width of the health bar"); ImGui::SameLine(); - if (ImGui::Button("Reset##EnemyHealthBarWidth")) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##EnemyHealthBarWidth", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value")); CVarClear(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::NewLine(); ImGui::EndTable(); } } + UIWidgets::PopStyleHeader(); } void Reset_Option_Single(const char* Button_Title, const char* name) { ImGui::SameLine(); - if (ImGui::Button(Button_Title)) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button(Button_Title, + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(name); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } void Reset_Option_Double(const char* Button_Title, const char* name) { ImGui::SameLine(); - if (ImGui::Button(Button_Title)) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button(Button_Title, + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear((std::string(name) + ".Value").c_str()); CVarClear((std::string(name) + ".Changed").c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } void DrawSillyTab() { ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"))) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - UIWidgets::Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for December holidays.\nWill reset on restart outside of December 23-25."); + UIWidgets::CVarCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for December holidays.\nWill reset on restart outside of December 23-25.")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Link Body Scale: %.3fx", "##Link_BodyScale", CVAR_COSMETIC("Link.BodyScale.Value"), 0.001f, 0.025f, "", 0.01f, true)) { - CVarSetInteger(CVAR_COSMETIC("Link.BodyScale.Changed"), 1); + if (UIWidgets::CVarSliderFloat("Link Body Size", CVAR_COSMETIC("Link.BodySize.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.3f") + .Min(0.001f) + .Max(0.05f) + .DefaultValue(0.01f) + .Step(0.001f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { + CVarSetInteger(CVAR_COSMETIC("Link.BodySize.Changed"), 1); } ImGui::SameLine(); - if (ImGui::Button("Reset##Link_BodyScale")) { - CVarClear(CVAR_COSMETIC("Link.BodyScale.Value")); - CVarClear(CVAR_COSMETIC("Link.BodyScale.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##Link_BodySize", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { + CVarClear(CVAR_COSMETIC("Link.BodySize.Value")); + CVarClear(CVAR_COSMETIC("Link.BodySize.Changed")); if (gPlayState != nullptr) { static Player* player = GET_PLAYER(gPlayState); player->actor.scale.x = 0.01f; @@ -1508,62 +1591,135 @@ void DrawSillyTab() { } } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - if (UIWidgets::EnhancementSliderFloat("Link Head Scale: %.2fx", "##Link_HeadScale", CVAR_COSMETIC("Link.HeadScale.Value"), 0.4f, 4.0f, "", 1.0f, false)) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); + if (UIWidgets::CVarSliderFloat("Link Head Scale", CVAR_COSMETIC("Link.HeadScale.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Link.HeadScale.Changed"), 1); } Reset_Option_Double("Reset##Link_HeadScale", CVAR_COSMETIC("Link.HeadScale")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Link Sword Scale: %f", "##Link_SwordScale", CVAR_COSMETIC("Link.SwordScale.Value"), 1.0f, 2.5f, "", 1.0f, false)) { + if (UIWidgets::CVarSliderFloat("Link Sword Scale", CVAR_COSMETIC("Link.SwordScale.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Link.SwordScale.Changed"), 1); } Reset_Option_Double("Reset##Link_SwordScale", CVAR_COSMETIC("Link.SwordScale")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Bunny Hood Length: %f", "##BunnyHood_EarLength", CVAR_COSMETIC("BunnyHood.EarLength"), -300.0f, 1000.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Bunny Hood Length", CVAR_COSMETIC("BunnyHood.EarLength"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(-300.0f) + .Max(1000.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##BunnyHood_EarLength", CVAR_COSMETIC("BunnyHood.EarLength")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Bunny Hood Spread: %f", "##BunnyHood_EarSpread", CVAR_COSMETIC("BunnyHood.EarSpread"), -300.0f, 500.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Bunny Hood Spread", CVAR_COSMETIC("BunnyHood.EarSpread"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(-300.0f) + .Max(500.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##BunnyHood_EarSpread", CVAR_COSMETIC("BunnyHood.EarSpread")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Goron Neck Length: %f", "##Goron_NeckLength", CVAR_COSMETIC("Goron.NeckLength"), 0.0f, 5000.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Goron Neck Length", CVAR_COSMETIC("Goron.NeckLength"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(0.0f) + .Max(5000.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Goron_NeckLength", CVAR_COSMETIC("Goron.NeckLength")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementCheckbox("Unfix Goron Spin", CVAR_COSMETIC("UnfixGoronSpin")); + UIWidgets::CVarCheckbox("Unfix Goron Spin", CVAR_COSMETIC("UnfixGoronSpin"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR)); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Fairies Size: %f", "##Fairies_Size", CVAR_COSMETIC("Fairies.Size"), 0.25f, 5.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("Fairies Size", CVAR_COSMETIC("Fairies.Size"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Fairies_Size", CVAR_COSMETIC("Fairies.Size")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("N64 Logo Spin Speed: %f", "##N64Logo_SpinSpeed", CVAR_COSMETIC("N64Logo.SpinSpeed"), 0.25f, 5.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("N64 Logo Spin Speed", CVAR_COSMETIC("N64Logo.SpinSpeed"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##N64Logo_SpinSpeed", CVAR_COSMETIC("N64Logo.SpinSpeed")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Moon Size: %f", "##Moon_Size", CVAR_COSMETIC("Moon.Size"), 0.5f, 2.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("Moon Size", CVAR_COSMETIC("Moon.Size"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Moon_Size", CVAR_COSMETIC("Moon.Size")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Kak Windmill Speed: %f", "##Kak_Windmill_Speed", CVAR_COSMETIC("Kak.Windmill_Speed.Value"), 100.0f, 6000.0f, "", 100.0f, false)) { + if (UIWidgets::CVarSliderFloat("Kak Windmill Speed", CVAR_COSMETIC("Kak.Windmill_Speed.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(100.0f) + .Max(6000.0f) + .DefaultValue(100.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Kak.Windmill_Speed.Changed"), 1); } Reset_Option_Double("Reset##Kak_Windmill_Speed", CVAR_COSMETIC("Kak.Windmill_Speed")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); ImGui::EndDisabled(); } @@ -1582,7 +1738,7 @@ void CopyMultipliedColor(CosmeticOption& cosmeticOptionSrc, CosmeticOption& cosm cosmeticOptionTarget.currentColor.z = newColor.b / 255.0f; cosmeticOptionTarget.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOptionTarget.cvar, newColor); + CVarSetColor(cosmeticOptionTarget.valuesCvar, newColor); CVarSetInteger((cosmeticOptionTarget.rainbowCvar), 0); CVarSetInteger((cosmeticOptionTarget.changedCvar), 1); } @@ -1642,7 +1798,7 @@ void RandomizeColor(CosmeticOption& cosmeticOption) { cosmeticOption.currentColor.z = newColor.b / 255.0f; cosmeticOption.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOption.cvar, newColor); + CVarSetColor(cosmeticOption.valuesCvar, newColor); CVarSetInteger(cosmeticOption.rainbowCvar, 0); CVarSetInteger(cosmeticOption.changedCvar, 1); ApplySideEffects(cosmeticOption); @@ -1658,12 +1814,12 @@ void ResetColor(CosmeticOption& cosmeticOption) { CVarClear(cosmeticOption.changedCvar); CVarClear(cosmeticOption.rainbowCvar); CVarClear(cosmeticOption.lockedCvar); - CVarClear(cosmeticOption.cvar); - CVarClear((std::string(cosmeticOption.cvar) + ".R").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".G").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".B").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".A").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".Type").c_str()); + CVarClear(cosmeticOption.valuesCvar); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".R").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".G").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".B").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".A").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".Type").c_str()); // This portion should match 1:1 the multiplied colors in `ApplySideEffect()` if (cosmeticOption.label == "Bow Body") { @@ -1709,40 +1865,28 @@ void ResetColor(CosmeticOption& cosmeticOption) { } void DrawCosmeticRow(CosmeticOption& cosmeticOption) { - bool colorChanged; - if (cosmeticOption.supportsAlpha) { - colorChanged = ImGui::ColorEdit4(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); - } else { - colorChanged = ImGui::ColorEdit3(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); - } - if (colorChanged) { - Color_RGBA8 color; - color.r = static_cast(cosmeticOption.currentColor.x * 255.0f); - color.g = static_cast(cosmeticOption.currentColor.y * 255.0f); - color.b = static_cast(cosmeticOption.currentColor.z * 255.0f); - color.a = static_cast(cosmeticOption.currentColor.w * 255.0f); - - CVarSetColor(cosmeticOption.cvar, color); + if (UIWidgets::CVarColorPicker(cosmeticOption.label.c_str(), cosmeticOption.cvar, + cosmeticOption.defaultColor, + cosmeticOption.supportsAlpha, 0, THEME_COLOR)) { CVarSetInteger((cosmeticOption.rainbowCvar), 0); CVarSetInteger((cosmeticOption.changedCvar), 1); ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::SameLine(); - ImGui::Text("%s", cosmeticOption.label.c_str()); //the longest option name - ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow) Color").x * 1.0f) + 60.0f); - if (ImGui::Button(("Random##" + cosmeticOption.label).c_str())) { + ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f); + if (UIWidgets::Button( + ("Random##" + cosmeticOption.label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) { RandomizeColor(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } if (cosmeticOption.supportsRainbow) { ImGui::SameLine(); - bool isRainbow = (bool)CVarGetInteger((cosmeticOption.rainbowCvar), 0); - if (ImGui::Checkbox(("Rainbow##" + cosmeticOption.label).c_str(), &isRainbow)) { - CVarSetInteger((cosmeticOption.rainbowCvar), isRainbow); + if (UIWidgets::CVarCheckbox(("Rainbow##" + cosmeticOption.label).c_str(), cosmeticOption.rainbowCvar, + UIWidgets::CheckboxOptions().Color(THEME_COLOR))) { CVarSetInteger((cosmeticOption.changedCvar), 1); ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); @@ -1750,14 +1894,14 @@ void DrawCosmeticRow(CosmeticOption& cosmeticOption) { } } ImGui::SameLine(); - bool isLocked = (bool)CVarGetInteger((cosmeticOption.lockedCvar), 0); - if (ImGui::Checkbox(("Locked##" + cosmeticOption.label).c_str(), &isLocked)) { - CVarSetInteger((cosmeticOption.lockedCvar), isLocked); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } + + UIWidgets::CVarCheckbox(("Locked##" + cosmeticOption.label).c_str(), cosmeticOption.lockedCvar, + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + if (CVarGetInteger((cosmeticOption.changedCvar), 0)) { ImGui::SameLine(); - if (ImGui::Button(("Reset##" + cosmeticOption.label).c_str())) { + if (UIWidgets::Button(("Reset##" + cosmeticOption.label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) { ResetColor(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); @@ -1769,32 +1913,35 @@ void DrawCosmeticGroup(CosmeticGroup cosmeticGroup) { std::string label = groupLabels.at(cosmeticGroup); ImGui::Text("%s", label.c_str()); // the longest option name - ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow) Color").x * 1.0f) + 60.0f); - if (ImGui::Button(("Random##" + label).c_str())) { + ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f); + if (UIWidgets::Button(("Random##" + label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if (cosmeticOption.group == cosmeticGroup && (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) && !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { + if (cosmeticOption.group == cosmeticGroup && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) && + !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { RandomizeColor(cosmeticOption); } } ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button(("Reset##" + label).c_str())) { + if (UIWidgets::Button(("Reset##" + label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.group == cosmeticGroup && !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { ResetColor(cosmeticOption); } } ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::Spacer(); for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.group == cosmeticGroup && (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { DrawCosmeticRow(cosmeticOption); } } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); } static const char* colorSchemes[2] = { @@ -1807,193 +1954,199 @@ void CosmeticsEditorWindow::ApplyDungeonKeyColors() { ResetColor(cosmeticOptions.at("Key.KeyringRing")); // Forest Temple - CVarSetColor(cosmeticOptions["Key.ForestSmallBody"].cvar, { 4, 195, 46, 255 }); + CVarSetColor(cosmeticOptions["Key.ForestSmallBody"].valuesCvar, { 4, 195, 46, 255 }); CVarSetInteger(cosmeticOptions["Key.ForestSmallBody"].changedCvar, 1); cosmeticOptions["Key.ForestSmallBody"].currentColor = { 4 / 255.0f, 195 / 255.0f, 46 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.ForestSmallEmblem")); ResetColor(cosmeticOptions.at("Key.ForestBossBody")); - CVarSetColor(cosmeticOptions["Key.ForestBossGem"].cvar, { 0, 255, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.ForestBossGem"].valuesCvar, { 0, 255, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.ForestBossGem"].changedCvar, 1); cosmeticOptions["Key.ForestBossGem"].currentColor = { 0, 255 / 255.0f, 0, 255 / 255.0f }; // Fire Temple - CVarSetColor(cosmeticOptions["Key.FireSmallBody"].cvar, { 237, 95, 95, 255 }); + CVarSetColor(cosmeticOptions["Key.FireSmallBody"].valuesCvar, { 237, 95, 95, 255 }); CVarSetInteger(cosmeticOptions["Key.FireSmallBody"].changedCvar, 1); cosmeticOptions["Key.FireSmallBody"].currentColor = { 237 / 255.0f, 95 / 255.0f, 95 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.FireSmallEmblem")); ResetColor(cosmeticOptions.at("Key.FireBossBody")); - CVarSetColor(cosmeticOptions["Key.FireBossGem"].cvar, { 255, 30, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.FireBossGem"].valuesCvar, { 255, 30, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.FireBossGem"].changedCvar, 1); cosmeticOptions["Key.FireBossGem"].currentColor = { 255 / 255.0f, 30 / 255.0f, 0, 255 / 255.0f }; // Water Temple - CVarSetColor(cosmeticOptions["Key.WaterSmallBody"].cvar, { 85, 180, 223, 255 }); + CVarSetColor(cosmeticOptions["Key.WaterSmallBody"].valuesCvar, { 85, 180, 223, 255 }); CVarSetInteger(cosmeticOptions["Key.WaterSmallBody"].changedCvar, 1); cosmeticOptions["Key.WaterSmallBody"].currentColor = { 85 / 255.0f, 180 / 255.0f, 223 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.WaterSmallEmblem")); ResetColor(cosmeticOptions.at("Key.WaterBossBody")); - CVarSetColor(cosmeticOptions["Key.WaterBossGem"].cvar, { 0, 137, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.WaterBossGem"].valuesCvar, { 0, 137, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.WaterBossGem"].changedCvar, 1); cosmeticOptions["Key.WaterBossGem"].currentColor = { 0, 137 / 255.0f, 255 / 255.0f, 255 / 255.0f }; // Spirit Temple - CVarSetColor(cosmeticOptions["Key.SpiritSmallBody"].cvar, { 222, 158, 47, 255 }); + CVarSetColor(cosmeticOptions["Key.SpiritSmallBody"].valuesCvar, { 222, 158, 47, 255 }); CVarSetInteger(cosmeticOptions["Key.SpiritSmallBody"].changedCvar, 1); cosmeticOptions["Key.SpiritSmallBody"].currentColor = { 222 / 255.0f, 158 / 255.0f, 47 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.SpiritSmallEmblem")); ResetColor(cosmeticOptions.at("Key.SpiritBossBody")); - CVarSetColor(cosmeticOptions["Key.SpiritBossGem"].cvar, { 255, 85, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.SpiritBossGem"].valuesCvar, { 255, 85, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.SpiritBossGem"].changedCvar, 1); cosmeticOptions["Key.SpiritBossGem"].currentColor = { 255 / 255.0f, 85 / 255.0f, 0, 255 / 255.0f }; // Shadow Temple - CVarSetColor(cosmeticOptions["Key.ShadowSmallBody"].cvar, { 126, 16, 177, 255 }); + CVarSetColor(cosmeticOptions["Key.ShadowSmallBody"].valuesCvar, { 126, 16, 177, 255 }); CVarSetInteger(cosmeticOptions["Key.ShadowSmallBody"].changedCvar, 1); cosmeticOptions["Key.ShadowSmallBody"].currentColor = { 126 / 255.0f, 16 / 255.0f, 177 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.ShadowSmallEmblem")); ResetColor(cosmeticOptions.at("Key.ShadowBossBody")); - CVarSetColor(cosmeticOptions["Key.ShadowBossGem"].cvar, { 153, 0, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.ShadowBossGem"].valuesCvar, { 153, 0, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.ShadowBossGem"].changedCvar, 1); cosmeticOptions["Key.ShadowBossGem"].currentColor = { 153 / 255.0f, 0, 255 / 255.0f, 255 / 255.0f }; // Ganon's Tower - CVarSetColor(cosmeticOptions["Key.GanonsSmallBody"].cvar, { 80, 80, 80, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsSmallBody"].valuesCvar, { 80, 80, 80, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsSmallBody"].changedCvar, 1); cosmeticOptions["Key.GanonsSmallBody"].currentColor = { 80 / 255.0f, 80 / 255.0f, 80 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.GanonsSmallEmblem")); - CVarSetColor(cosmeticOptions["Key.GanonsBossBody"].cvar, { 80, 80, 80, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsBossBody"].valuesCvar, { 80, 80, 80, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsBossBody"].changedCvar, 1); cosmeticOptions["Key.GanonsBossBody"].currentColor = { 80 / 255.0f, 80 / 255.0f, 80 / 255.0f, 255 / 255.0f }; - CVarSetColor(cosmeticOptions["Key.GanonsBossGem"].cvar, { 255, 0, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsBossGem"].valuesCvar, { 255, 0, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsBossGem"].changedCvar, 1); cosmeticOptions["Key.GanonsBossGem"].currentColor = { 255 / 255.0f, 0, 0, 255 / 255.0f }; // Bottom of the Well - CVarSetColor(cosmeticOptions["Key.WellSmallBody"].cvar, { 227, 110, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.WellSmallBody"].valuesCvar, { 227, 110, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.WellSmallBody"].changedCvar, 1); cosmeticOptions["Key.WellSmallBody"].currentColor = { 227 / 255.0f, 110 / 255.0f, 255 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.WellSmallEmblem")); // Gerudo Training Ground - CVarSetColor(cosmeticOptions["Key.GTGSmallBody"].cvar, { 221, 212, 60, 255 }); + CVarSetColor(cosmeticOptions["Key.GTGSmallBody"].valuesCvar, { 221, 212, 60, 255 }); CVarSetInteger(cosmeticOptions["Key.GTGSmallBody"].changedCvar, 1); cosmeticOptions["Key.GTGSmallBody"].currentColor = { 221 / 255.0f, 212 / 255.0f, 60 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.GTGSmallEmblem")); // Gerudo Fortress - CVarSetColor(cosmeticOptions["Key.FortSmallBody"].cvar, { 255, 255, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.FortSmallBody"].valuesCvar, { 255, 255, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.FortSmallBody"].changedCvar, 1); cosmeticOptions["Key.FortSmallBody"].currentColor = { 255 / 255.0f, 255 / 255.0f, 255 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.FortSmallEmblem")); } void CosmeticsEditorWindow::DrawElement() { - ImGui::Text("Color Scheme"); - ImGui::SameLine(); - UIWidgets::EnhancementCombobox(CVAR_COSMETIC("DefaultColorScheme"), colorSchemes, COLORSCHEME_N64); - UIWidgets::EnhancementCheckbox("Advanced Mode", CVAR_COSMETIC("AdvancedMode")); - UIWidgets::InsertHelpHoverText( - "Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic option.\n\n" - "For example, if you have custom Link model, then the Link's Hair color option will most likely not apply." - ); - + UIWidgets::CVarCombobox("Color Scheme", CVAR_COSMETIC("DefaultColorScheme"), colorSchemes, + UIWidgets::ComboboxOptions() + .DefaultIndex(COLORSCHEME_N64) + .Color(THEME_COLOR) + .LabelPosition(UIWidgets::LabelPosition::Near) + .ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + UIWidgets::CVarCheckbox("Sync Rainbow colors", CVAR_COSMETIC("RainbowSync"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR)); + UIWidgets::CVarSliderFloat("Rainbow Speed", CVAR_COSMETIC("RainbowSpeed"), + UIWidgets::FloatSliderOptions() + .Format("%.2f") + .Min(0.01f) + .Max(1.0f) + .DefaultValue(0.6f) + .Step(0.01f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Randomize All on New Scene", CVAR_COSMETIC("RandomizeAllOnNewScene"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Enables randomizing all unlocked cosmetics when you enter a new scene.")); + UIWidgets::CVarCheckbox( + "Advanced Mode", CVAR_COSMETIC("AdvancedMode"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic " + "option.\n\n" + "For example, if you have custom Link model, then the Link's Hair color option will most likely not " + "apply.")); if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { - if (ImGui::Button("Lock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Lock All Advanced", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.advancedOption) { CVarSetInteger(cosmeticOption.lockedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Unlock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Unlock All Advanced", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.advancedOption) { CVarSetInteger(cosmeticOption.lockedCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } - UIWidgets::EnhancementCheckbox("Sync Rainbow colors", CVAR_COSMETIC("RainbowSync")); - UIWidgets::EnhancementSliderFloat("Rainbow Speed: %.3f", "##rainbowSpeed", CVAR_COSMETIC("RainbowSpeed"), 0.03f, 1.0f, "", 0.6f, false, true); - UIWidgets::EnhancementCheckbox("Randomize All on New Scene", CVAR_COSMETIC("RandomizeAllOnNewScene")); - UIWidgets::Tooltip("Enables randomizing all unlocked cosmetics when you enter a new scene."); - - if (ImGui::Button("Randomize All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Randomize All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { CosmeticsEditor_RandomizeAll(); } ImGui::SameLine(); - if (ImGui::Button("Reset All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { - for (auto& [id, cosmeticOption] : cosmeticOptions) { - if (!CVarGetInteger(cosmeticOption.lockedCvar, 0)) { - ResetColor(cosmeticOption); - } - } + if (UIWidgets::Button("Reset All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { + CVarClearBlock("gCosmetics"); ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - - if (ImGui::Button("Lock All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Lock All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { CVarSetInteger(cosmeticOption.lockedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Unlock All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Unlock All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { CVarSetInteger(cosmeticOption.lockedCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (ImGui::Button("Rainbow All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Rainbow All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if ( - !CVarGetInteger(cosmeticOption.lockedCvar, 0) && - ( - !cosmeticOption.advancedOption || - CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0) - ) - ) { + if (!CVarGetInteger(cosmeticOption.lockedCvar, 0) && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { CVarSetInteger(cosmeticOption.rainbowCvar, 1); CVarSetInteger(cosmeticOption.changedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Un-Rainbow All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Un-Rainbow All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if ( - !CVarGetInteger(cosmeticOption.lockedCvar, 0) && - ( - !cosmeticOption.advancedOption || - CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0) - ) - ) { + if (!CVarGetInteger(cosmeticOption.lockedCvar, 0) && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { CVarSetInteger(cosmeticOption.rainbowCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::Spacer(3.0f); + + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("CosmeticsContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Link & Items")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_LINK); DrawCosmeticGroup(COSMETICS_GROUP_GLOVES); @@ -2006,14 +2159,15 @@ void CosmeticsEditorWindow::DrawElement() { if (ImGui::BeginTabItem("Keys")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (ImGui::Button("Give all keys dungeon-specific colors", ImVec2(300.0f, 30.0f))) { + if (UIWidgets::Button( + "Give all keys dungeon-specific colors", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { ApplyDungeonKeyColors(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_KEYRING); DrawCosmeticGroup(COSMETICS_GROUP_SMALL_KEYS); @@ -2024,29 +2178,37 @@ void CosmeticsEditorWindow::DrawElement() { if (ImGui::BeginTabItem("Effects")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_MAGIC); DrawCosmeticGroup(COSMETICS_GROUP_ARROWS); DrawCosmeticGroup(COSMETICS_GROUP_SPIN_ATTACK); DrawCosmeticGroup(COSMETICS_GROUP_TRAILS); - if (UIWidgets::EnhancementSliderInt("Trails Duration: %d", "##Trails_Duration", CVAR_COSMETIC("Trails.Duration.Value"), 2, 20, "", 4)) { + if (UIWidgets::CVarSliderInt("Trails Duration: %d", CVAR_COSMETIC("Trails.Duration.Value"), + UIWidgets::IntSliderOptions() + .Min(2) + .Max(20) + .DefaultValue(4) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Trails.Duration.Changed"), 1); } ImGui::SameLine(); - if (ImGui::Button("Reset##Trails_Duration")) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##Trails_Duration", UIWidgets::ButtonOptions() + .Size(ImVec2(80, 36)) + .Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("Trails.Duration.Value")); CVarClear(CVAR_COSMETIC("Trails.Duration.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("World & NPCs")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_WORLD); DrawCosmeticGroup(COSMETICS_GROUP_NAVI); @@ -2060,7 +2222,7 @@ void CosmeticsEditorWindow::DrawElement() { } if (ImGui::BeginTabItem("HUD")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_HUD); DrawCosmeticGroup(COSMETICS_GROUP_TITLE); @@ -2074,6 +2236,7 @@ void CosmeticsEditorWindow::DrawElement() { if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { if (ImGui::BeginTabItem("Pause Menu")) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_KALEIDO); ImGui::EndTabItem(); } @@ -2081,12 +2244,14 @@ void CosmeticsEditorWindow::DrawElement() { if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { if (ImGui::BeginTabItem("Message")) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_MESSAGE); ImGui::EndTabItem(); } } ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); } void RegisterOnLoadGameHook() { @@ -2113,7 +2278,7 @@ void CosmeticsEditorWindow::InitElement() { // Convert the `current color` into the format that the ImGui color picker expects for (auto& [id, cosmeticOption] : cosmeticOptions) { Color_RGBA8 defaultColor = {cosmeticOption.defaultColor.r, cosmeticOption.defaultColor.g, cosmeticOption.defaultColor.b, cosmeticOption.defaultColor.a}; - Color_RGBA8 cvarColor = CVarGetColor(cosmeticOption.cvar, defaultColor); + Color_RGBA8 cvarColor = CVarGetColor(cosmeticOption.valuesCvar, defaultColor); cosmeticOption.currentColor.x = cvarColor.r / 255.0f; cosmeticOption.currentColor.y = cvarColor.g / 255.0f; diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h index c9a28ef29..9c3dd76ba 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h @@ -52,7 +52,6 @@ static float TablesCellsWidth = 300.0f; static ImGuiTableColumnFlags FlagsTable = ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV; static ImGuiTableColumnFlags FlagsCell = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort; -ImVec4 GetRandomValue(); void CosmeticsEditor_RandomizeAll(); void CosmeticsEditor_RandomizeGroup(CosmeticGroup group); void CosmeticsEditor_ResetAll(); diff --git a/soh/soh/Enhancements/debugger/MessageViewer.cpp b/soh/soh/Enhancements/debugger/MessageViewer.cpp index 589dbaf4a..49764374f 100644 --- a/soh/soh/Enhancements/debugger/MessageViewer.cpp +++ b/soh/soh/Enhancements/debugger/MessageViewer.cpp @@ -1,6 +1,9 @@ #include "MessageViewer.h" -#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "soh/OTRGlobals.h" + #include #include "../custom-message/CustomMessageManager.h" @@ -13,6 +16,8 @@ extern "C" u8 sMessageHasSetSfx; +using namespace UIWidgets; + void MessageViewer::InitElement() { CustomMessageManager::Instance->AddCustomMessageTable(TABLE_ID); mTableIdBuf = static_cast(calloc(MAX_STRING_SIZE, sizeof(char))); @@ -21,8 +26,10 @@ void MessageViewer::InitElement() { } void MessageViewer::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); ImGui::Text("Table ID"); ImGui::SameLine(); + PushStyleInput(THEME_COLOR); ImGui::InputText("##TableID", mTableIdBuf, MAX_STRING_SIZE, ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterAlphaNum); UIWidgets::InsertHelpHoverText("Leave blank for vanilla table"); ImGui::Text("Text ID"); @@ -38,6 +45,8 @@ void MessageViewer::DrawElement() { UIWidgets::InsertHelpHoverText("Hexadecimal Text ID of the message to load. Hexadecimal digits only (0-9/A-F)."); break; } + PopStyleInput(); + PushStyleCheckbox(THEME_COLOR); if (ImGui::RadioButton("Hexadecimal", &mTextIdBase, HEXADECIMAL)) { memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); } @@ -45,8 +54,10 @@ void MessageViewer::DrawElement() { if (ImGui::RadioButton("Decimal", &mTextIdBase, DECIMAL)) { memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); } + PopStyleCheckbox(); ImGui::Text("Language"); ImGui::SameLine(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##Language", mLanguages[mLanguage])) { // ReSharper disable CppDFAUnreachableCode for (size_t i = 0; i < mLanguages.size(); i++) { @@ -58,7 +69,9 @@ void MessageViewer::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); UIWidgets::InsertHelpHoverText("Which language to load from the selected text ID"); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Display Message##ExistingMessage")) { mDisplayExistingMessageClicked = true; } @@ -66,11 +79,14 @@ void MessageViewer::DrawElement() { UIWidgets::InsertHelpHoverText("Enter a string using Custom Message Syntax to preview it in-game. " "Any newline (\\n) characters inserted by the Enter key will be stripped " "from the output."); + PushStyleInput(THEME_COLOR); ImGui::InputTextMultiline("##CustomMessage", mCustomMessageBuf, MAX_STRING_SIZE); + PopStyleInput(); if (ImGui::Button("Display Message##CustomMessage")) { mDisplayCustomMessageClicked = true; } - // ReSharper restore CppDFAUnreachableCode + PopStyleButton(); + ImGui::PopFont(); } void MessageViewer::UpdateElement() { diff --git a/soh/soh/Enhancements/debugger/actorViewer.cpp b/soh/soh/Enhancements/debugger/actorViewer.cpp index 8aac60954..a154df65b 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.cpp +++ b/soh/soh/Enhancements/debugger/actorViewer.cpp @@ -1,6 +1,7 @@ #include "actorViewer.h" #include "../../util.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/ActorDB.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/nametag.h" @@ -59,6 +60,8 @@ std::array acMapping = { "Chest" }; +using namespace UIWidgets; + typedef enum { ACTORVIEWER_NAMETAGS_NONE, ACTORVIEWER_NAMETAGS_DESC, @@ -70,29 +73,18 @@ const std::string GetActorDescription(u16 id) { return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.desc : "???"; } -template void DrawGroupWithBorder(T&& drawFunc) { +template void DrawGroupWithBorder(T&& drawFunc, std::string section) { // First group encapsulates the inner portion and border - ImGui::BeginGroup(); - - ImVec2 padding = ImGui::GetStyle().FramePadding; - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(ImVec2(p0.x + padding.x, p0.y + padding.y)); + ImGui::BeginChild(std::string("##" + section).c_str(), ImVec2(0, 0), + ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY); // Second group encapsulates just the inner portion ImGui::BeginGroup(); - + ImGui::AlignTextToFramePadding(); drawFunc(); - - ImGui::Dummy(padding); ImGui::EndGroup(); - ImVec2 p1 = ImGui::GetItemRectMax(); - p1.x += padding.x; - ImVec4 borderCol = ImGui::GetStyle().Colors[ImGuiCol_Border]; - ImGui::GetWindowDrawList()->AddRect( - p0, p1, IM_COL32(borderCol.x * 255, borderCol.y * 255, borderCol.z * 255, borderCol.w * 255)); - - ImGui::EndGroup(); + ImGui::EndChild(); } void PopulateActorDropdown(int i, std::vector& data) { @@ -938,10 +930,12 @@ void ActorViewerWindow::DrawElement() { static std::string filler = "Please select"; static std::vector list; static u16 lastSceneId = 0; - static char searchString[64] = ""; + static std::string searchString = ""; static s16 currentSelectedInDropdown; static std::vector actors; + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + if (gPlayState != nullptr) { needs_reset = lastSceneId != gPlayState->sceneNum; if (needs_reset) { @@ -951,13 +945,13 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; - for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { - searchString[i] = 0; - } + searchString = ""; currentSelectedInDropdown = -1; actors.clear(); } lastSceneId = gPlayState->sceneNum; + + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Actor Type", acMapping[category])) { for (int i = 0; i < acMapping.size(); i++) { if (ImGui::Selectable(acMapping[i])) { @@ -990,7 +984,9 @@ void ActorViewerWindow::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); + PushStyleHeader(THEME_COLOR); if (ImGui::TreeNode("Selected Actor")) { DrawGroupWithBorder([&]() { ImGui::Text("Name: %s", ActorDB::Instance->RetrieveEntry(display->id).name.c_str()); @@ -998,46 +994,52 @@ void ActorViewerWindow::DrawElement() { ImGui::Text("Category: %s", acMapping[display->category]); ImGui::Text("ID: %d", display->id); ImGui::Text("Parameters: %d", display->params); - }); - + }, "Selected Actor"); + ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); ImGui::Text("Actor Position"); - ImGui::InputScalar("x pos", ImGuiDataType_Float, &display->world.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("y pos", ImGuiDataType_Float, &display->world.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("z pos", ImGuiDataType_Float, &display->world.pos.z); - }); - + ImGui::InputScalar("X##CurPos", ImGuiDataType_Float, &display->world.pos.x); + ImGui::InputScalar("Y##CurPos", ImGuiDataType_Float, &display->world.pos.y); + ImGui::InputScalar("Z##CurPos", ImGuiDataType_Float, &display->world.pos.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "Actor Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); ImGui::Text("Actor Rotation"); - ImGui::InputScalar("x rot", ImGuiDataType_S16, &display->world.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("y rot", ImGuiDataType_S16, &display->world.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("z rot", ImGuiDataType_S16, &display->world.rot.z); - }); + ImGui::InputScalar("X##CurRot", ImGuiDataType_S16, &display->world.rot.x); + ImGui::InputScalar("Y##CurRot", ImGuiDataType_S16, &display->world.rot.y); + ImGui::InputScalar("Z##CurRot", ImGuiDataType_S16, &display->world.rot.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "Actor Rotation"); if (display->category == ACTORCAT_BOSS || display->category == ACTORCAT_ENEMY) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Enemy Health", ImGuiDataType_U8, &display->colChkInfo.health); + PopStyleInput(); UIWidgets::InsertHelpHoverText("Some actors might not use this!"); } DrawGroupWithBorder([&]() { ImGui::Text("flags"); UIWidgets::DrawFlagArray32("flags", display->flags); - }); + }, "flags"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("bgCheckFlags"); UIWidgets::DrawFlagArray16("bgCheckFlags", display->bgCheckFlags); - }); + }, "bgCheckFlags"); - if (ImGui::Button("Refresh")) { + if (Button("Refresh", ButtonOptions().Color(THEME_COLOR))) { PopulateActorDropdown(category, list); switch (rm) { case INTERACT: @@ -1053,13 +1055,13 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Go to Actor")) { + if (Button("Go to Actor", ButtonOptions().Color(THEME_COLOR))) { Player* player = GET_PLAYER(gPlayState); Math_Vec3f_Copy(&player->actor.world.pos, &display->world.pos); Math_Vec3f_Copy(&player->actor.home.pos, &player->actor.world.pos); } - if (ImGui::Button("Fetch from Target")) { + if (Button("Fetch from Target", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor with target arrow above it. You might need C-Up for enemies"))) { Player* player = GET_PLAYER(gPlayState); fetch = player->talkActor; if (fetch != NULL) { @@ -1069,8 +1071,7 @@ void ActorViewerWindow::DrawElement() { rm = TARGET; } } - UIWidgets::InsertHelpHoverText("Grabs actor with target arrow above it. You might need C-Up for enemies"); - if (ImGui::Button("Fetch from Held")) { + if (Button("Fetch from Held", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor that Link is holding"))) { Player* player = GET_PLAYER(gPlayState); fetch = player->heldActor; if (fetch != NULL) { @@ -1080,8 +1081,7 @@ void ActorViewerWindow::DrawElement() { rm = HELD; } } - UIWidgets::InsertHelpHoverText("Grabs actor that Link is holding"); - if (ImGui::Button("Fetch from Interaction")) { + if (Button("Fetch from Interaction", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor from \"interaction range\""))) { Player* player = GET_PLAYER(gPlayState); fetch = player->interactRangeActor; if (fetch != NULL) { @@ -1091,21 +1091,21 @@ void ActorViewerWindow::DrawElement() { rm = INTERACT; } } - UIWidgets::InsertHelpHoverText("Grabs actor from \"interaction range\""); ImGui::TreePop(); } if (ImGui::TreeNode("New...")) { - ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + //ImGui::PushItemWidth(ImGui::GetFontSize() * 10); - if (ImGui::InputText("Search Actor", searchString, ARRAY_COUNT(searchString))) { - actors = GetActorsWithDescriptionContainingString(std::string(searchString)); + if (InputString("Search Actor", &searchString, InputOptions().Color(THEME_COLOR))) { + actors = GetActorsWithDescriptionContainingString(searchString); currentSelectedInDropdown = -1; } - if (searchString[0] != 0 && !actors.empty()) { + if (!SohUtils::IsStringEmpty(searchString) && !actors.empty()) { std::string preview = currentSelectedInDropdown == -1 ? "Please Select" : ActorDB::Instance->RetrieveEntry(actors[currentSelectedInDropdown]).desc; + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Results", preview.c_str())) { for (u8 i = 0; i < actors.size(); i++) { if (ImGui::Selectable( @@ -1118,6 +1118,7 @@ void ActorViewerWindow::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); } ImGui::Text("%s", GetActorDescription(newActor.id).c_str()); @@ -1125,44 +1126,51 @@ void ActorViewerWindow::DrawElement() { newActor.params = 0; } - UIWidgets::EnhancementCheckbox("Advanced mode", CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams")); - UIWidgets::InsertHelpHoverText("Changes the actor specific param menus with a direct input"); + CVarCheckbox("Advanced mode", CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams"), CheckboxOptions().Tooltip("Changes the actor specific param menus with a direct input")); if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams"), 0)) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + PopStyleInput(); } else if (std::find(noParamsActors.begin(), noParamsActors.end(), newActor.id) == noParamsActors.end()) { CreateActorSpecificData(); if (actorSpecificData.find(newActor.id) == actorSpecificData.end()) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + PopStyleInput(); } else { DrawGroupWithBorder([&]() { ImGui::Text("Actor Specific Data"); newActor.params = actorSpecificData[newActor.id](newActor.params); - }); + }, "Actor Specific Data"); } } ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); ImGui::Text("New Actor Position"); - ImGui::InputScalar("posX", ImGuiDataType_Float, &newActor.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("posY", ImGuiDataType_Float, &newActor.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("posZ", ImGuiDataType_Float, &newActor.pos.z); - }); - + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + ImGui::InputScalar("X##NewPos", ImGuiDataType_Float, &newActor.pos.x); + ImGui::InputScalar("Y##NewPos", ImGuiDataType_Float, &newActor.pos.y); + ImGui::InputScalar("Z##NewPos", ImGuiDataType_Float, &newActor.pos.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "New Actor Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); ImGui::Text("New Actor Rotation"); - ImGui::InputScalar("rotX", ImGuiDataType_S16, &newActor.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("rotY", ImGuiDataType_S16, &newActor.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("rotZ", ImGuiDataType_S16, &newActor.rot.z); - }); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + ImGui::InputScalar("X##NewRot", ImGuiDataType_S16, &newActor.rot.x); + ImGui::InputScalar("Y##NewRot", ImGuiDataType_S16, &newActor.rot.y); + ImGui::InputScalar("Z##NewRot", ImGuiDataType_S16, &newActor.rot.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "New Actor Rotation"); - if (ImGui::Button("Fetch from Link")) { + if (Button("Fetch from Link", ButtonOptions().Color(THEME_COLOR))) { Player* player = GET_PLAYER(gPlayState); Vec3f newPos = player->actor.world.pos; Vec3s newRot = player->actor.world.rot; @@ -1170,7 +1178,7 @@ void ActorViewerWindow::DrawElement() { newActor.rot = newRot; } - if (ImGui::Button("Spawn")) { + if (Button("Spawn", ButtonOptions().Color(THEME_COLOR))) { if (ActorDB::Instance->RetrieveEntry(newActor.id).entry.valid) { Actor_Spawn(&gPlayState->actorCtx, gPlayState, newActor.id, newActor.pos.x, newActor.pos.y, newActor.pos.z, newActor.rot.x, newActor.rot.y, newActor.rot.z, newActor.params, 0); @@ -1179,7 +1187,7 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Spawn as Child")) { + if (Button("Spawn as Child", ButtonOptions().Color(THEME_COLOR))) { Actor* parent = display; if (parent != NULL) { if (newActor.id >= 0 && newActor.id < ACTOR_ID_MAX && @@ -1193,28 +1201,26 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Reset")) { + if (Button("Reset", ButtonOptions().Color(THEME_COLOR))) { newActor = { 0, 0, { 0, 0, 0 }, { 0, 0, 0 } }; } ImGui::TreePop(); } + PopStyleHeader(); - static const char* nameTagOptions[] = { - "None", - "Short Description", - "Actor ID", - "Both" + static std::unordered_map nameTagOptions = { + { 0, "None" }, + { 1, "Short Description" }, + { 2, "Actor ID" }, + { 3, "Both" }, }; - UIWidgets::Spacer(0); - - ImGui::Text("Actor Name Tags"); - if (UIWidgets::EnhancementCombobox(CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), nameTagOptions, ACTORVIEWER_NAMETAGS_NONE)) { + if (CVarCombobox("Actor Name Tags", CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), nameTagOptions, + ComboboxOptions().Color(THEME_COLOR).Tooltip("Adds \"name tags\" above actors for identification"))) { NameTag_RemoveAllByTag(DEBUG_ACTOR_NAMETAG_TAG); ActorViewer_AddTagForAllActors(); } - UIWidgets::Tooltip("Adds \"name tags\" above actors for identification"); } else { ImGui::Text("Global Context needed for actor info!"); if (needs_reset) { @@ -1223,13 +1229,12 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; - for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { - searchString[i] = 0; - } + searchString = ""; currentSelectedInDropdown = -1; actors.clear(); } } + ImGui::PopFont(); } void ActorViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/colViewer.cpp b/soh/soh/Enhancements/debugger/colViewer.cpp index 493ffdfdc..de2272598 100644 --- a/soh/soh/Enhancements/debugger/colViewer.cpp +++ b/soh/soh/Enhancements/debugger/colViewer.cpp @@ -1,6 +1,7 @@ #include "colViewer.h" #include "../../frame_interpolation.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -19,12 +20,12 @@ extern "C" { extern PlayState* gPlayState; } -enum class ColRenderSetting { Disabled, Solid, Transparent }; +typedef enum ColRenderSetting { ColRenderDisabled, ColRenderSolid, ColRenderTransparent } ColRenderSetting ; -static const char* ColRenderSettingNames[] = { - "Disabled", - "Solid", - "Transparent", +static std::unordered_map ColRenderSettingNames = { + { ColRenderDisabled, "Disabled" }, + { ColRenderSolid, "Solid" }, + { ColRenderTransparent, "Transparent" }, }; ImVec4 scene_col; @@ -53,45 +54,69 @@ static std::vector cylinderVtx; static std::vector sphereGfx; static std::vector sphereVtx; +using namespace UIWidgets; + // Draws the ImGui window for the collision viewer void ColViewerWindow::DrawElement() { - UIWidgets::EnhancementCheckbox("Enabled", CVAR_DEVELOPER_TOOLS("ColViewer.Enabled")); + CheckboxOptions checkOpt = CheckboxOptions().Color(THEME_COLOR); + ComboboxOptions comboOpt = ComboboxOptions().Color(THEME_COLOR); + CVarCheckbox("Enabled", CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), checkOpt); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Scene", CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Bg Actors", CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Col Check", CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), ColRenderSettingNames, COLVIEW_DISABLED); + CVarCombobox("Scene", CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), ColRenderSettingNames, comboOpt); + CVarCombobox("Bg Actors", CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), ColRenderSettingNames, comboOpt); + CVarCombobox("Col Check", CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), ColRenderSettingNames, comboOpt); + CVarCombobox("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), ColRenderSettingNames, comboOpt); - UIWidgets::EnhancementCheckbox("Apply as decal", CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::InsertHelpHoverText("Applies the collision as a decal display. This can be useful if there is z-fighting occuring " - "with the scene geometry, but can cause other artifacts."); - UIWidgets::EnhancementCheckbox("Shaded", CVAR_DEVELOPER_TOOLS("ColViewer.Shaded")); - UIWidgets::InsertHelpHoverText("Applies the scene's shading to the collision display."); + CVarCheckbox("Apply as decal", CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), + checkOpt.DefaultValue(true).Tooltip("Applies the collision as a decal display. This can be useful if there is z-fighting occuring " + "with the scene geometry, but can cause other artifacts.")); + CVarCheckbox("Shaded", CVAR_DEVELOPER_TOOLS("ColViewer.Shaded"), checkOpt.DefaultValue(false).Tooltip("Applies the scene's shading to the collision display.")); // This has to be duplicated in both code paths due to the nature of ImGui::IsItemHovered() const std::string colorHelpText = "View and change the colors used for collision display."; + PushStyleHeader(THEME_COLOR); if (ImGui::TreeNode("Colors")) { - UIWidgets::InsertHelpHoverText(colorHelpText); + UIWidgets::Tooltip(colorHelpText.c_str()); - UIWidgets::EnhancementColor("Normal", CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), scene_col, ImVec4(255, 255, 255, 255), false); - UIWidgets::EnhancementColor("Hookshot", CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), hookshot_col, ImVec4(128, 128, 255, 255), - false); - UIWidgets::EnhancementColor("Entrance", CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), entrance_col, ImVec4(0, 255, 0, 255), false); - UIWidgets::EnhancementColor("Special Surface (Grass/Sand/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), - specialSurface_col, ImVec4(192, 255, 192, 255), false); - UIWidgets::EnhancementColor("Interactable (Vines/Crawlspace/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), - interactable_col, ImVec4(192, 0, 192, 255), false); - UIWidgets::EnhancementColor("Slope", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), slope_col, ImVec4(255, 255, 128, 255), false); - UIWidgets::EnhancementColor("Void", CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), void_col, ImVec4(255, 0, 0, 255), false); - UIWidgets::EnhancementColor("OC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), oc_col, ImVec4(255, 255, 255, 255), false); - UIWidgets::EnhancementColor("AC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), ac_col, ImVec4(0, 0, 255, 255), false); - UIWidgets::EnhancementColor("AT", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), at_col, ImVec4(255, 0, 0, 255), false); - UIWidgets::EnhancementColor("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), waterbox_col, ImVec4(0, 0, 255, 255), false); + if (CVarColorPicker("Normal", CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + scene_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 })); + } + if (CVarColorPicker("Hookshot", CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + hookshot_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 })); + } + if (CVarColorPicker("Entrance", CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + entrance_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 })); + } + if (CVarColorPicker("Special Surface (Grass/Sand/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + specialSurface_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 })); + } + if (CVarColorPicker("Interactable (Vines/Crawlspace/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), { 192, 0, 192, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + interactable_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), { 192, 0, 192, 255 })); + } + if (CVarColorPicker("Slope", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + slope_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 })); + } + if (CVarColorPicker("Void", CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + void_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 })); + } + if (CVarColorPicker("OC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + oc_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 })); + } + if (CVarColorPicker("AC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + ac_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 })); + } + if (CVarColorPicker("AT", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 255, 0, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + at_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 255, 0, 0, 255 })); + } + if (CVarColorPicker("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + waterbox_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 })); + } ImGui::TreePop(); } else { - UIWidgets::InsertHelpHoverText(colorHelpText); + UIWidgets::Tooltip(colorHelpText.c_str()); } + PopStyleHeader(); } // Calculates the normal for a triangle at the 3 specified points @@ -287,7 +312,7 @@ void InitGfx(std::vector& gfx, ColRenderSetting setting) { uint64_t cm; uint32_t gm; - if (setting == ColRenderSetting::Transparent) { + if (setting == ColRenderTransparent) { rm = Z_CMP | IM_RD | CVG_DST_FULL | FORCE_BL; blc1 = GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA); blc2 = GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA); @@ -301,7 +326,7 @@ void InitGfx(std::vector& gfx, ColRenderSetting setting) { if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), 1) != 0) { rm |= ZMODE_DEC; - } else if (setting == ColRenderSetting::Transparent) { + } else if (setting == ColRenderTransparent) { rm |= ZMODE_XLU; } else { rm |= ZMODE_OPA; @@ -340,21 +365,21 @@ void DrawDynapoly(std::vector& dl, CollisionHeader* col, int32_t bgId) { CollisionPoly* poly = &col->polyList[i]; if (SurfaceType_IsHookshotSurface(&gPlayState->colCtx, poly, bgId)) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot.Value"), { 128, 128, 255, 255 }); } else if (func_80041D94(&gPlayState->colCtx, poly, bgId) > 0x01) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), {192, 0, 192, 255}); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable.Value"), {192, 0, 192, 255}); } else if (func_80041E80(&gPlayState->colCtx, poly, bgId) == 0x0C) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid.Value"), { 255, 0, 0, 255 }); } else if (SurfaceType_GetSceneExitIndex(&gPlayState->colCtx, poly, bgId) || func_80041E80(&gPlayState->colCtx, poly, bgId) == 0x05) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance.Value"), { 0, 255, 0, 255 }); } else if (func_80041D4C(&gPlayState->colCtx, poly, bgId) != 0 || SurfaceType_IsWallDamage(&gPlayState->colCtx, poly, bgId)) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface.Value"), { 192, 255, 192, 255 }); } else if (SurfaceType_GetSlope(&gPlayState->colCtx, poly, bgId) == 0x01) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope.Value"), { 255, 255, 128, 255 }); } else { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal.Value"), { 255, 255, 255, 255 }); } if (color.r != lastColorR || color.g != lastColorG || color.b != lastColorB) { @@ -404,11 +429,11 @@ void DrawDynapoly(std::vector& dl, CollisionHeader* col, int32_t bgId) { void DrawSceneCollision() { ColRenderSetting showSceneColSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), COLVIEW_DISABLED); - if (showSceneColSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showSceneColSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showSceneColSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showSceneColSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showSceneColSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); @@ -418,11 +443,11 @@ void DrawSceneCollision() { // Draws all Bg Actors void DrawBgActorCollision() { ColRenderSetting showBgActorSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), COLVIEW_DISABLED); - if (showBgActorSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showBgActorSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showBgActorSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showBgActorSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showBgActorSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); @@ -543,22 +568,22 @@ void DrawColCheckList(std::vector& dl, Collider** objects, int32_t count) { // Draws all Col Check objects void DrawColCheckCollision() { ColRenderSetting showColCheckSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), COLVIEW_DISABLED); - if (showColCheckSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showColCheckSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showColCheckSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showColCheckSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showColCheckSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); CollisionCheckContext& col = gPlayState->colChkCtx; - Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 }); + Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC.Value"), { 255, 255, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colOC, col.colOCCount); - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colAC, col.colACCount); - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 0, 0, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colAT, col.colATCount); @@ -595,15 +620,15 @@ extern "C" f32 zdWaterBoxMinY; // Draws all waterboxes void DrawWaterboxList() { ColRenderSetting showWaterboxSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), COLVIEW_DISABLED); - if (showWaterboxSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showWaterboxSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showWaterboxSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showWaterboxSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showWaterboxSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); - Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 }); + Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index b9858aff4..dfc4d9681 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -3,6 +3,7 @@ #include "soh/SohGui/ImGuiUtils.h" #include "soh/OTRGlobals.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -27,7 +28,7 @@ extern PlayState* gPlayState; } // Maps entries in the GS flag array to the area name it represents -std::vector gsMapping = { +std::vector gsMapping = { "Deku Tree", "Dodongo's Cavern", "Inside Jabu-Jabu's Belly", @@ -56,6 +57,15 @@ extern "C" u8 gAreaGsFlags[]; extern "C" u8 gAmmoItems[]; +#define IMAGE_SIZE 48.0f + +using namespace UIWidgets; + +IntSliderOptions intSliderOptionsBase; +ButtonOptions buttonOptionsBase; +CheckboxOptions checkboxOptionsBase; +ComboboxOptions comboboxOptionsBase; + // Modification of gAmmoItems that replaces ITEM_NONE with the item in inventory slot it represents u8 gAllAmmoItems[] = { ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_BOW, ITEM_ARROW_FIRE, ITEM_DINS_FIRE, @@ -65,28 +75,17 @@ u8 gAllAmmoItems[] = { // Encapsulates what is drawn by the passed-in function within a border template -void DrawGroupWithBorder(T&& drawFunc) { +void DrawGroupWithBorder(T&& drawFunc, std::string section) { // First group encapsulates the inner portion and border + ImGui::BeginChild(std::string("##" + section).c_str(), ImVec2(0, 0), + ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY); + ImGui::BeginGroup(); - - ImVec2 padding = ImGui::GetStyle().FramePadding; - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(ImVec2(p0.x + padding.x, p0.y + padding.y)); - - // Second group encapsulates just the inner portion - ImGui::BeginGroup(); - + ImGui::AlignTextToFramePadding(); drawFunc(); - - ImGui::Dummy(padding); ImGui::EndGroup(); - ImVec2 p1 = ImGui::GetItemRectMax(); - p1.x += padding.x; - ImVec4 borderCol = ImGui::GetStyle().Colors[ImGuiCol_Border]; - ImGui::GetWindowDrawList()->AddRect(p0, p1, IM_COL32(borderCol.x * 255, borderCol.y * 255, borderCol.z * 255, borderCol.w * 255)); - - ImGui::EndGroup(); + ImGui::EndChild(); } char z2ASCII(int code) { @@ -108,6 +107,42 @@ char z2ASCII(int code) { } +typedef enum MagicLevel { + MAGIC_LEVEL_NONE, + MAGIC_LEVEL_SINGLE, + MAGIC_LEVEL_DOUBLE +}; + +std::unordered_map magicLevelMap = { + { MAGIC_LEVEL_NONE, "None" }, + { MAGIC_LEVEL_SINGLE, "Single" }, + { MAGIC_LEVEL_DOUBLE, "Double" }, +}; + +typedef enum AudioOutput { + AUDIO_STEREO, + AUDIO_MONO, + AUDIO_HEADSET, + AUDIO_SURROUND, +}; + +std::unordered_map audioMap = { + { AUDIO_STEREO, "Stereo" }, + { AUDIO_MONO, "Mono" }, + { AUDIO_HEADSET, "Headset" }, + { AUDIO_SURROUND, "Surround" }, +}; + +typedef enum ZTarget { + Z_TARGET_SWITCH, + Z_TARGET_HOLD, +}; + +std::unordered_map zTargetMap = { + { Z_TARGET_SWITCH, "Switch" }, + { Z_TARGET_HOLD, "Hold" }, +}; + void DrawInfoTab() { // TODO Needs a better method for name changing but for now this will work. std::string name; @@ -121,200 +156,147 @@ void DrawInfoTab() { ImGui::PushItemWidth(ImGui::GetFontSize() * 6); ImGui::Text("Name: %s", name.c_str()); - UIWidgets::InsertHelpHoverText("Player Name"); + Tooltip("Player Name"); std::string nameID; for (int i = 0; i < 8; i++) { nameID = z2ASCII(i); if (i % 4 != 0) { ImGui::SameLine(); } + PushStyleInput(THEME_COLOR); ImGui::InputScalar(nameID.c_str(), ImGuiDataType_U8, &gSaveContext.playerName[i], &one, NULL); + PopStyleInput(); } // Use an intermediary to keep the health from updating (and potentially killing the player) // until it is done being edited int16_t healthIntermediary = gSaveContext.healthCapacity; + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Max Health", ImGuiDataType_S16, &healthIntermediary); + PopStyleInput(); if (ImGui::IsItemDeactivated()) { gSaveContext.healthCapacity = healthIntermediary; } - UIWidgets::InsertHelpHoverText("Maximum health. 16 units per full heart"); + Tooltip("Maximum health. 16 units per full heart"); if (gSaveContext.health > gSaveContext.healthCapacity) { gSaveContext.health = gSaveContext.healthCapacity; // Clamp health to new max } - - const uint16_t healthMin = 0; - const uint16_t healthMax = gSaveContext.healthCapacity; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Health", ImGuiDataType_S16, &gSaveContext.health, &healthMin, &healthMax); - UIWidgets::InsertHelpHoverText("Current health. 16 units per full heart"); + int32_t health = (int32_t)gSaveContext.health; + if (SliderInt("Health", &health, intSliderOptionsBase.Tooltip("Current health. 16 units per full heart") + .Min(0).Max(gSaveContext.healthCapacity))) { + gSaveContext.health = (int16_t)health; + } bool isDoubleDefenseAcquired = gSaveContext.isDoubleDefenseAcquired != 0; - if (ImGui::Checkbox("Double Defense", &isDoubleDefenseAcquired)) { + if (Checkbox("Double Defense", &isDoubleDefenseAcquired, checkboxOptionsBase.Tooltip("Is double defense unlocked?"))) { gSaveContext.isDoubleDefenseAcquired = isDoubleDefenseAcquired; gSaveContext.inventory.defenseHearts = gSaveContext.isDoubleDefenseAcquired ? 20 : 0; // Set to get the border drawn in the UI } - UIWidgets::InsertHelpHoverText("Is double defense unlocked?"); - - std::string magicName; - if (gSaveContext.magicLevel == 2) { - magicName = "Double"; - } else if (gSaveContext.magicLevel == 1) { - magicName = "Single"; - } else { - magicName = "None"; + if (Combobox("Magic Level", &gSaveContext.magicLevel, magicLevelMap, comboboxOptionsBase.Tooltip("Current magic level"))) { + gSaveContext.isMagicAcquired = gSaveContext.magicLevel > 0; + gSaveContext.isDoubleMagicAcquired = gSaveContext.magicLevel == 2; } - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6); - if (ImGui::BeginCombo("Magic Level", magicName.c_str())) { - if (ImGui::Selectable("Double")) { - gSaveContext.magicLevel = 2; - gSaveContext.isMagicAcquired = true; - gSaveContext.isDoubleMagicAcquired = true; - } - if (ImGui::Selectable("Single")) { - gSaveContext.magicLevel = 1; - gSaveContext.isMagicAcquired = true; - gSaveContext.isDoubleMagicAcquired = false; - } - if (ImGui::Selectable("None")) { - gSaveContext.magicLevel = 0; - gSaveContext.isMagicAcquired = false; - gSaveContext.isDoubleMagicAcquired = false; - } - - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Current magic level"); gSaveContext.magicCapacity = gSaveContext.magicLevel * 0x30; // Set to get the bar drawn in the UI if (gSaveContext.magic > gSaveContext.magicCapacity) { gSaveContext.magic = gSaveContext.magicCapacity; // Clamp magic to new max } - const uint8_t magicMin = 0; - const uint8_t magicMax = gSaveContext.magicCapacity; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Magic", ImGuiDataType_S8, &gSaveContext.magic, &magicMin, &magicMax); - UIWidgets::InsertHelpHoverText("Current magic. 48 units per magic level"); + int32_t magic = (int32_t)gSaveContext.magic; + if (SliderInt("Magic", &magic, intSliderOptionsBase.Min(0).Max(gSaveContext.magicCapacity).Tooltip("Current magic. 48 units per magic level"))) { + gSaveContext.magic = (int8_t)magic; + } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Rupees", ImGuiDataType_S16, &gSaveContext.rupees); - UIWidgets::InsertHelpHoverText("Current rupees"); + Tooltip("Current rupees"); + PopStyleInput(); - const uint16_t dayTimeMin = 0; - const uint16_t dayTimeMax = 0xFFFF; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Time", ImGuiDataType_U16, &gSaveContext.dayTime, &dayTimeMin, &dayTimeMax); - UIWidgets::InsertHelpHoverText("Time of day"); - if (ImGui::Button("Dawn")) { + SliderInt("Time", (int32_t*) &gSaveContext.dayTime, intSliderOptionsBase.Min(0).Max(0xFFFF).Tooltip("Time of day")); + if (Button("Dawn", buttonOptionsBase)) { gSaveContext.dayTime = 0x4000; } ImGui::SameLine(); - if (ImGui::Button("Noon")) { + if (Button("Noon", buttonOptionsBase)) { gSaveContext.dayTime = 0x8000; } ImGui::SameLine(); - if (ImGui::Button("Sunset")) { + if (Button("Sunset", buttonOptionsBase)) { gSaveContext.dayTime = 0xC001; } ImGui::SameLine(); - if (ImGui::Button("Midnight")) { + if (Button("Midnight", buttonOptionsBase)) { gSaveContext.dayTime = 0; } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Total Days", ImGuiDataType_S32, &gSaveContext.totalDays); - UIWidgets::InsertHelpHoverText("Total number of days elapsed since the start of the game"); + Tooltip("Total number of days elapsed since the start of the game"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Deaths", ImGuiDataType_U16, &gSaveContext.deaths); - UIWidgets::InsertHelpHoverText("Total number of deaths"); + Tooltip("Total number of deaths"); + PopStyleInput(); - bool bgsFlag = gSaveContext.bgsFlag != 0; - if (ImGui::Checkbox("Has BGS", &bgsFlag)) { - gSaveContext.bgsFlag = bgsFlag; - } - UIWidgets::InsertHelpHoverText("Is Biggoron sword unlocked? Replaces Giant's knife"); + Checkbox("Has BGS", (bool*) &gSaveContext.bgsFlag, checkboxOptionsBase.Tooltip("Is Biggoron sword unlocked? Replaces Giant's knife")); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Sword Health", ImGuiDataType_U16, &gSaveContext.swordHealth); - UIWidgets::InsertHelpHoverText("Giant's knife health. Default is 8. Must be >0 for Biggoron sword to work"); + Tooltip("Giant's knife health. Default is 8. Must be >0 for Biggoron sword to work"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Bgs Day Count", ImGuiDataType_S32, &gSaveContext.bgsDayCount); - UIWidgets::InsertHelpHoverText("Total number of days elapsed since giving Biggoron the claim check"); + Tooltip("Total number of days elapsed since giving Biggoron the claim check"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Entrance Index", ImGuiDataType_S32, &gSaveContext.entranceIndex); - UIWidgets::InsertHelpHoverText("From which entrance did Link arrive?"); + Tooltip("From which entrance did Link arrive?"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Cutscene Index", ImGuiDataType_S32, &gSaveContext.cutsceneIndex); - UIWidgets::InsertHelpHoverText("Which cutscene is this?"); + Tooltip("Which cutscene is this?"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Navi Timer", ImGuiDataType_U16, &gSaveContext.naviTimer); - UIWidgets::InsertHelpHoverText("Navi wants to talk at 600 units, decides not to at 3000."); + Tooltip("Navi wants to talk at 600 units, decides not to at 3000."); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 1 State", ImGuiDataType_S16, &gSaveContext.timer1State); - UIWidgets::InsertHelpHoverText("Heat timer, race timer, etc. Has white font"); + Tooltip("Heat timer, race timer, etc. Has white font"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 1 Value", ImGuiDataType_S16, &gSaveContext.timer1Value, &one, NULL); - UIWidgets::InsertHelpHoverText("Time, in seconds"); + Tooltip("Time, in seconds"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 2 State", ImGuiDataType_S16, &gSaveContext.timer2State); - UIWidgets::InsertHelpHoverText("Trade timer, Ganon collapse timer, etc. Has yellow font"); + Tooltip("Trade timer, Ganon collapse timer, etc. Has yellow font"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 2 Value", ImGuiDataType_S16, &gSaveContext.timer2Value, &one, NULL); - UIWidgets::InsertHelpHoverText("Time, in seconds"); - - const char* audioName; - switch (gSaveContext.audioSetting) { - case 0: - audioName = "Stereo"; - break; - case 1: - audioName = "Mono"; - break; - case 2: - audioName = "Headset"; - break; - case 3: - audioName = "Surround"; - break; - default: - audioName = "?"; - } - if (ImGui::BeginCombo("Audio", audioName)) { - if (ImGui::Selectable("Stereo")) { - gSaveContext.audioSetting = 0; - } - if (ImGui::Selectable("Mono")) { - gSaveContext.audioSetting = 1; - } - if (ImGui::Selectable("Headset")) { - gSaveContext.audioSetting = 2; - } - if (ImGui::Selectable("Surround")) { - gSaveContext.audioSetting = 3; - } + Tooltip("Time, in seconds"); + PopStyleInput(); - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Sound setting"); + Combobox("Audio", &gSaveContext.audioSetting, audioMap, comboboxOptionsBase.Tooltip("Sound setting")); - bool n64DDFlag = gSaveContext.n64ddFlag != 0; - if (ImGui::Checkbox("64 DD file?", &n64DDFlag)) { - gSaveContext.n64ddFlag = n64DDFlag; - } - UIWidgets::InsertHelpHoverText("WARNING! If you save, your file may be locked! Use caution!"); + Checkbox("64 DD file?", (bool*) &gSaveContext.n64ddFlag, checkboxOptionsBase.Tooltip("WARNING! If you save, your file may be locked! Use caution!")); - if (ImGui::BeginCombo("Z Target Mode", gSaveContext.zTargetSetting ? "Hold" : "Switch")) { - if (ImGui::Selectable("Switch")) { - gSaveContext.zTargetSetting = 0; - } - if (ImGui::Selectable("Hold")) { - gSaveContext.zTargetSetting = 1; - } - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Z-Targeting behavior"); + Combobox("Z Target Mode", &gSaveContext.zTargetSetting, zTargetMap, comboboxOptionsBase.Tooltip("Z-Targeting behavior")); if (IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT)) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Triforce Pieces", ImGuiDataType_U8, &gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); - UIWidgets::InsertHelpHoverText("Currently obtained Triforce Pieces. For Triforce Hunt."); + Tooltip("Currently obtained Triforce Pieces. For Triforce Hunt."); + PopStyleInput(); } ImGui::PushItemWidth(ImGui::GetFontSize() * 10); @@ -330,78 +312,79 @@ void DrawInfoTab() { for (int i = 0; i < 7; i++) { if(i == 2 && ImGui::TreeNode("Fishing") ){ //fishing has a few more flags to it u8 fishSize = gSaveContext.highScores[i] & 0x7F; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Child Size Record",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0x7F; gSaveContext.highScores[i]|=fishSize & 0x7F; } char fishMsg[64]; std::sprintf(fishMsg,"Weight: %2.0f lbs",((SQ(fishSize)*.0036)+.5)); - UIWidgets::InsertHelpHoverText(fishMsg); + Tooltip(fishMsg); + PopStyleInput(); bool FishBool = gSaveContext.highScores[i]&0x80; - if (ImGui::Checkbox("Cheated as Child", &FishBool)) { + if (Checkbox("Cheated as Child", &FishBool, checkboxOptionsBase.Tooltip("Used the Sinking lure to catch it."))) { gSaveContext.highScores[i] &= ~0x80; gSaveContext.highScores[i] |= (0x80 * FishBool); } - UIWidgets::InsertHelpHoverText("Used the Sinking lure to catch it."); fishSize=(gSaveContext.highScores[i] & 0x7F000000)>>0x18; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Adult Size Record",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0x7F000000; gSaveContext.highScores[i]|=(fishSize & 0x7F) << 0x18; } std::sprintf(fishMsg,"Weight: %2.0f lbs",((SQ(fishSize)*.0036)+.5)); - UIWidgets::InsertHelpHoverText(fishMsg); + Tooltip(fishMsg); + PopStyleInput(); FishBool = gSaveContext.highScores[i] & 0x80000000; - if (ImGui::Checkbox("Cheated as Adult", &FishBool)) { + if (Checkbox("Cheated as Adult", &FishBool, checkboxOptionsBase.Tooltip("Used the Sinking lure to catch it."))) { gSaveContext.highScores[i] &= ~0x80000000; gSaveContext.highScores[i] |= (0x80000000 * FishBool); } - UIWidgets::InsertHelpHoverText("Used the Sinking lure to catch it."); FishBool = gSaveContext.highScores[i]&0x100; - if (ImGui::Checkbox("Played as Child", &FishBool)) { + if (Checkbox("Played as Child", &FishBool, checkboxOptionsBase.Tooltip("Played at least one game as a child"))) { gSaveContext.highScores[i] &= ~0x100; gSaveContext.highScores[i] |= (0x100 * FishBool); } - UIWidgets::InsertHelpHoverText("Played at least one game as a child"); FishBool = gSaveContext.highScores[i]&0x200; - if (ImGui::Checkbox("Played as Adult", &FishBool)) { + if (Checkbox("Played as Adult", &FishBool, checkboxOptionsBase.Tooltip("Played at least one game as an adult"))) { gSaveContext.highScores[i] &= ~0x200; gSaveContext.highScores[i] |= (0x200 * FishBool); } - UIWidgets::InsertHelpHoverText("Played at least one game as an adult"); FishBool = gSaveContext.highScores[i]&0x400; - if (ImGui::Checkbox("Got Prize as Child", &FishBool)) { + if (Checkbox("Got Prize as Child", &FishBool, checkboxOptionsBase.Tooltip("Got the prize item (Heart Piece, unless rando.)\nunlocks Sinking Lure for Child Link."))) { gSaveContext.highScores[i] &= ~0x400; gSaveContext.highScores[i] |= (0x400 * FishBool); } - UIWidgets::InsertHelpHoverText("Got the prize item (Heart Piece, unless rando.)\nunlocks Sinking Lure for Child Link."); FishBool = gSaveContext.highScores[i]&0x800; - if (ImGui::Checkbox("Got Prize as Adult", &FishBool)) { + if (Checkbox("Got Prize as Adult", &FishBool, checkboxOptionsBase.Tooltip("Got the prize item (Golden Scale, unless rando.)\nUnlocks Sinking Lure for Adult Link."))) { gSaveContext.highScores[i] &= ~0x800; gSaveContext.highScores[i] |= (0x800 * FishBool); } - UIWidgets::InsertHelpHoverText("Got the prize item (Golden Scale, unless rando.)\nUnlocks Sinking Lure for Adult Link."); FishBool = gSaveContext.highScores[i] & 0x1000; - if (ImGui::Checkbox("Stole Owner's Hat", &FishBool)) { + if (Checkbox("Stole Owner's Hat", &FishBool, checkboxOptionsBase.Tooltip("The owner's now visibly bald when Adult Link."))) { gSaveContext.highScores[i] &= ~0x1000; gSaveContext.highScores[i] |= (0x1000 * FishBool); } - UIWidgets::InsertHelpHoverText("The owner's now visibly bald when Adult Link."); fishSize=(gSaveContext.highScores[i] & 0xFF0000)>>16; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Times Played",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0xFF0000; gSaveContext.highScores[i]|=(fishSize) << 16; } - UIWidgets::InsertHelpHoverText("Determines weather and school size during dawn/dusk."); + Tooltip("Determines weather and school size during dawn/dusk."); + PopStyleInput(); ImGui::TreePop(); continue; } - if (i == 5) { //HS_UNK_05 is unused + if (i == 5 || i == 2) { //HS_UNK_05 is unused continue; } std::string minigameLbl = minigameHS[i]; + PushStyleInput(THEME_COLOR); ImGui::InputScalar(minigameLbl.c_str(), ImGuiDataType_S32, &gSaveContext.highScores[i], &one, NULL); + PopStyleInput(); } ImGui::TreePop(); @@ -412,14 +395,14 @@ void DrawInfoTab() { void DrawBGSItemFlag(uint8_t itemID) { const ItemMapEntry& slotEntry = itemMapping[itemID]; - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); } void DrawInventoryTab() { static bool restrictToValid = true; - ImGui::Checkbox("Restrict to valid items", &restrictToValid); - UIWidgets::InsertHelpHoverText("Restricts items and ammo to only what is possible to legally acquire in-game"); + Checkbox("Restrict to valid items", &restrictToValid, checkboxOptionsBase.Tooltip("Restricts items and ammo to only what is possible to legally acquire in-game")); for (int32_t y = 0; y < 4; y++) { for (int32_t x = 0; x < 6; x++) { @@ -433,35 +416,33 @@ void DrawInventoryTab() { ImGui::SameLine(); } - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); uint8_t item = gSaveContext.inventory.items[index]; + PushStyleButton(Colors::DarkGray); if (item != ITEM_NONE) { const ItemMapEntry& slotEntry = itemMapping.find(item)->second; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(48.0f, 48.0f), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { selectedIndex = index; ImGui::OpenPopup(itemPopupPicker); } } else { - if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##itemNone", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { selectedIndex = index; ImGui::OpenPopup(itemPopupPicker); } } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); + PopStyleButton(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); if (ImGui::BeginPopup(itemPopupPicker)) { - if (ImGui::Button("##itemNonePicker", ImVec2(32.0f, 32.0f))) { + PushStyleButton(Colors::DarkGray); + if (ImGui::Button("##itemNonePicker", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { gSaveContext.inventory.items[selectedIndex] = ITEM_NONE; ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText("None"); + PopStyleButton(); + UIWidgets::Tooltip("None"); std::vector possibleItems; if (restrictToValid) { @@ -486,15 +467,15 @@ void DrawInventoryTab() { ImGui::SameLine(); } const ItemMapEntry& slotEntry = possibleItems[pickerIndex]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); + PopStyleButton(); if (ret) { gSaveContext.inventory.items[selectedIndex] = slotEntry.id; ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id)); + UIWidgets::Tooltip(SohUtils::GetItemName(slotEntry.id).c_str()); } ImGui::EndPopup(); @@ -517,11 +498,13 @@ void DrawInventoryTab() { drawnAmmoItems++; ImGui::PushID(ammoIndex); - ImGui::PushItemWidth(32.0f); + ImGui::PushItemWidth(IMAGE_SIZE); ImGui::BeginGroup(); - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[item].name), ImVec2(32.0f, 32.0f)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[item].name), ImVec2(IMAGE_SIZE, IMAGE_SIZE)); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("##ammoInput", ImGuiDataType_S8, &AMMO(item)); + PopStyleInput(); ImGui::EndGroup(); ImGui::PopItemWidth(); @@ -548,7 +531,10 @@ void DrawFlagTableArray16(const FlagTable& flagTable, uint16_t row, uint16_t& fl ImGui::PushID(flagIndex); bool hasDescription = !!flagTable.flagDescriptions.contains(row * 16 + flagIndex); uint32_t bitMask = 1 << flagIndex; - ImGui::PushStyleColor(ImGuiCol_FrameBg, hasDescription ? ImVec4(0.16f, 0.29f, 0.48f, 0.54f) : ImVec4(0.16f, 0.29f, 0.48f, 0.24f)); + ImVec4 themeColor = ColorValues.at(THEME_COLOR); + ImVec4 colorDark = { themeColor.x * 0.4f, themeColor.y * 0.4f, themeColor.z * 0.4f , themeColor.z }; + PushStyleCheckbox(hasDescription ? themeColor : colorDark); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); bool flag = (flags & bitMask) != 0; if (ImGui::Checkbox("##check", &flag)) { if (flag) { @@ -557,7 +543,8 @@ void DrawFlagTableArray16(const FlagTable& flagTable, uint16_t row, uint16_t& fl flags &= ~bitMask; } } - ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + PopStyleCheckbox(); if (ImGui::IsItemHovered() && hasDescription) { ImGui::BeginTooltip(); ImGui::Text("%s", UIWidgets::WrappedText(flagTable.flagDescriptions.at(row * 16 + flagIndex), 60).c_str()); @@ -575,165 +562,154 @@ void DrawFlagsTab() { DrawGroupWithBorder([&]() { ImGui::Text("stateFlags1"); - UIWidgets::DrawFlagArray32("stateFlags1", player->stateFlags1); - }); + DrawFlagArray32("stateFlags1", player->stateFlags1, THEME_COLOR); + }, "stateFlags1"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("stateFlags2"); - UIWidgets::DrawFlagArray32("stateFlags2", player->stateFlags2); - }); + DrawFlagArray32("stateFlags2", player->stateFlags2, THEME_COLOR); + }, "stateFlags2"); DrawGroupWithBorder([&]() { ImGui::Text("stateFlags3"); - UIWidgets::DrawFlagArray8("stateFlags3", player->stateFlags3); - }); + DrawFlagArray8("stateFlags3", player->stateFlags3, THEME_COLOR); + }, "stateFlags3"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("unk_6AE_rotFlags"); - UIWidgets::DrawFlagArray16("unk_6AE_rotFlags", player->unk_6AE_rotFlags); - }); + DrawFlagArray16("unk_6AE_rotFlags", player->unk_6AE_rotFlags, THEME_COLOR); + }, "unk_6AE_rotFlags"); } ImGui::TreePop(); } if (ImGui::TreeNode("Current Scene")) { if (gPlayState != nullptr) { ActorContext* act = &gPlayState->actorCtx; - DrawGroupWithBorder([&]() { ImGui::Text("Switch"); - UIWidgets::InsertHelpHoverText("Permanently-saved switch flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Switch")) { + InsertHelpHoverText("Permanently-saved switch flags"); + if (Button("Set All##Switch", buttonOptionsBase.Tooltip(""))) { act->flags.swch = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Switch")) { + if (Button("Clear All##Switch", buttonOptionsBase.Tooltip(""))) { act->flags.swch = 0; } - UIWidgets::DrawFlagArray32("Switch", act->flags.swch); - }); + DrawFlagArray32("Switch", act->flags.swch, THEME_COLOR); + }, "Switch"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Switch"); - UIWidgets::InsertHelpHoverText("Temporary switch flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Switch")) { + InsertHelpHoverText("Temporary switch flags. Unset on scene transitions"); + if (Button("Set All##Temp Switch", buttonOptionsBase.Tooltip(""))) { act->flags.tempSwch = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Switch")) { + if (Button("Clear All##Temp Switch", buttonOptionsBase.Tooltip(""))) { act->flags.tempSwch = 0; } - UIWidgets::DrawFlagArray32("Temp Switch", act->flags.tempSwch); - }); + DrawFlagArray32("Temp Switch", act->flags.tempSwch, THEME_COLOR); + }, "Temp Switch"); DrawGroupWithBorder([&]() { ImGui::Text("Clear"); - UIWidgets::InsertHelpHoverText("Permanently-saved room-clear flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Clear")) { + InsertHelpHoverText("Permanently-saved room-clear flags"); + if (Button("Set All##Clear", buttonOptionsBase.Tooltip(""))) { act->flags.clear = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Clear")) { + if (Button("Clear All##Clear", buttonOptionsBase.Tooltip(""))) { act->flags.clear = 0; } - UIWidgets::DrawFlagArray32("Clear", act->flags.clear); - }); + DrawFlagArray32("Clear", act->flags.clear, THEME_COLOR); + }, "Clear"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Clear"); - UIWidgets::InsertHelpHoverText("Temporary room-clear flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Clear")) { + InsertHelpHoverText("Temporary room-clear flags. Unset on scene transitions"); + if (Button("Set All##Temp Clear", buttonOptionsBase.Tooltip(""))) { act->flags.tempClear = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Clear")) { + if (Button("Clear All##Temp Clear", buttonOptionsBase.Tooltip(""))) { act->flags.tempClear = 0; } - UIWidgets::DrawFlagArray32("Temp Clear", act->flags.tempClear); - }); + DrawFlagArray32("Temp Clear", act->flags.tempClear, THEME_COLOR); + }, "Temp Clear"); DrawGroupWithBorder([&]() { ImGui::Text("Collect"); - UIWidgets::InsertHelpHoverText("Permanently-saved collect flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Collect")) { + InsertHelpHoverText("Permanently-saved collect flags"); + if (Button("Set All##Collect", buttonOptionsBase.Tooltip(""))) { act->flags.collect = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Collect")) { + if (Button("Clear All##Collect", buttonOptionsBase.Tooltip(""))) { act->flags.collect = 0; } - UIWidgets::DrawFlagArray32("Collect", act->flags.collect); - }); + DrawFlagArray32("Collect", act->flags.collect, THEME_COLOR); + }, "Collect"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Collect"); - UIWidgets::InsertHelpHoverText("Temporary collect flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Collect")) { + InsertHelpHoverText("Temporary collect flags. Unset on scene transitions"); + if (Button("Set All##Temp Collect", buttonOptionsBase.Tooltip(""))) { act->flags.tempCollect = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Collect")) { + if (Button("Clear All##Temp Collect", buttonOptionsBase.Tooltip(""))) { act->flags.tempCollect = 0; } - UIWidgets::DrawFlagArray32("Temp Collect", act->flags.tempCollect); - }); + DrawFlagArray32("Temp Collect", act->flags.tempCollect, THEME_COLOR); + }, "Temp Collect"); DrawGroupWithBorder([&]() { ImGui::Text("Chest"); - UIWidgets::InsertHelpHoverText("Permanently-saved chest flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Chest")) { + InsertHelpHoverText("Permanently-saved chest flags"); + if (Button("Set All##Chest", buttonOptionsBase.Tooltip(""))) { act->flags.chest = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Chest")) { + if (Button("Clear All##Chest", buttonOptionsBase.Tooltip(""))) { act->flags.chest = 0; } - UIWidgets::DrawFlagArray32("Chest", act->flags.chest); - }); + DrawFlagArray32("Chest", act->flags.chest, THEME_COLOR); + }, "Chest"); ImGui::SameLine(); ImGui::BeginGroup(); - if (ImGui::Button("Reload Flags")) { + if (Button("Reload Flags", buttonOptionsBase.Tooltip("Load flags from saved scene flags. Normally happens on scene load"))) { act->flags.swch = gSaveContext.sceneFlags[gPlayState->sceneNum].swch; act->flags.clear = gSaveContext.sceneFlags[gPlayState->sceneNum].clear; act->flags.collect = gSaveContext.sceneFlags[gPlayState->sceneNum].collect; act->flags.chest = gSaveContext.sceneFlags[gPlayState->sceneNum].chest; } - UIWidgets::SetLastItemHoverText("Load flags from saved scene flags. Normally happens on scene load"); - if (ImGui::Button("Save Flags")) { + if (Button("Save Flags", buttonOptionsBase.Tooltip("Save current scene flags. Normally happens on scene exit"))) { gSaveContext.sceneFlags[gPlayState->sceneNum].swch = act->flags.swch; gSaveContext.sceneFlags[gPlayState->sceneNum].clear = act->flags.clear; gSaveContext.sceneFlags[gPlayState->sceneNum].collect = act->flags.collect; gSaveContext.sceneFlags[gPlayState->sceneNum].chest = act->flags.chest; } - UIWidgets::SetLastItemHoverText("Save current scene flags. Normally happens on scene exit"); - if (ImGui::Button("Clear Flags")) { + if (Button("Clear Flags", buttonOptionsBase.Tooltip("Clear current scene flags. Reload scene to see changes"))) { act->flags.swch = 0; act->flags.clear = 0; act->flags.collect = 0; act->flags.chest = 0; } - UIWidgets::SetLastItemHoverText("Clear current scene flags. Reload scene to see changes"); ImGui::EndGroup(); @@ -746,8 +722,10 @@ void DrawFlagsTab() { if (ImGui::TreeNode("Saved Scene Flags")) { static uint32_t selectedSceneFlagMap = 0; + ImGui::AlignTextToFramePadding(); ImGui::Text("Map"); ImGui::SameLine(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##Map", SohUtils::GetSceneName(selectedSceneFlagMap).c_str())) { for (int32_t sceneIndex = 0; sceneIndex < SCENE_ID_MAX; sceneIndex++) { if (ImGui::Selectable(SohUtils::GetSceneName(sceneIndex).c_str())) { @@ -757,77 +735,68 @@ void DrawFlagsTab() { ImGui::EndCombo(); } + PopStyleCombobox(); // Don't show current scene button if there is no current scene if (gPlayState != nullptr) { ImGui::SameLine(); - if (ImGui::Button("Current")) { + if (Button("Current", buttonOptionsBase.Tooltip("Open flags for current scene"))) { selectedSceneFlagMap = gPlayState->sceneNum; } - UIWidgets::SetLastItemHoverText("Open flags for current scene"); } DrawGroupWithBorder([&]() { ImGui::Text("Switch"); - UIWidgets::InsertHelpHoverText("Switch flags"); - UIWidgets::DrawFlagArray32("Switch", gSaveContext.sceneFlags[selectedSceneFlagMap].swch); - }); + InsertHelpHoverText("Switch flags"); + DrawFlagArray32("Switch", gSaveContext.sceneFlags[selectedSceneFlagMap].swch, THEME_COLOR); + }, "Saved Switch"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Clear"); - UIWidgets::InsertHelpHoverText("Room-clear flags"); - UIWidgets::DrawFlagArray32("Clear", gSaveContext.sceneFlags[selectedSceneFlagMap].clear); - }); + InsertHelpHoverText("Room-clear flags"); + DrawFlagArray32("Clear", gSaveContext.sceneFlags[selectedSceneFlagMap].clear, THEME_COLOR); + }, "Saved Clear"); DrawGroupWithBorder([&]() { ImGui::Text("Collect"); - UIWidgets::InsertHelpHoverText("Collect flags"); - UIWidgets::DrawFlagArray32("Collect", gSaveContext.sceneFlags[selectedSceneFlagMap].collect); - }); + InsertHelpHoverText("Collect flags"); + DrawFlagArray32("Collect", gSaveContext.sceneFlags[selectedSceneFlagMap].collect, THEME_COLOR); + }, "Saved Collect"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Chest"); - UIWidgets::InsertHelpHoverText("Chest flags"); - UIWidgets::DrawFlagArray32("Chest", gSaveContext.sceneFlags[selectedSceneFlagMap].chest); - }); + InsertHelpHoverText("Chest flags"); + DrawFlagArray32("Chest", gSaveContext.sceneFlags[selectedSceneFlagMap].chest, THEME_COLOR); + }, "Saved Chest"); DrawGroupWithBorder([&]() { ImGui::Text("Rooms"); - UIWidgets::InsertHelpHoverText("Flags for visted rooms"); - UIWidgets::DrawFlagArray32("Rooms", gSaveContext.sceneFlags[selectedSceneFlagMap].rooms); - }); + InsertHelpHoverText("Flags for visted rooms"); + DrawFlagArray32("Rooms", gSaveContext.sceneFlags[selectedSceneFlagMap].rooms, THEME_COLOR); + }, "Saved Rooms"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Floors"); - UIWidgets::InsertHelpHoverText("Flags for visted floors"); - UIWidgets::DrawFlagArray32("Floors", gSaveContext.sceneFlags[selectedSceneFlagMap].floors); - }); + InsertHelpHoverText("Flags for visted floors"); + DrawFlagArray32("Floors", gSaveContext.sceneFlags[selectedSceneFlagMap].floors, THEME_COLOR); + }, "Saved Floors"); ImGui::TreePop(); } DrawGroupWithBorder([&]() { - static uint32_t selectedGsMap = 0; + size_t selectedGsMap = 0; ImGui::Text("Gold Skulltulas"); - ImGui::Text("Map"); - ImGui::SameLine(); - if (ImGui::BeginCombo("##Gold Skulltula Map", gsMapping[selectedGsMap].c_str())) { - for (int32_t gsIndex = 0; gsIndex < gsMapping.size(); gsIndex++) { - if (ImGui::Selectable(gsMapping[gsIndex].c_str())) { - selectedGsMap = gsIndex; - } - } - - ImGui::EndCombo(); - } + Combobox("Map##Gold Skulltulas", &selectedGsMap, gsMapping, comboboxOptionsBase.Tooltip("")); // TODO We should write out descriptions for each one... ugh + ImGui::AlignTextToFramePadding(); ImGui::Text("Flags"); uint32_t currentFlags = GET_GS_FLAGS(selectedGsMap); uint32_t allFlags = gAreaGsFlags[selectedGsMap]; @@ -838,6 +807,8 @@ void DrawFlagsTab() { ImGui::SameLine(); ImGui::PushID(allFlags); + PushStyleCheckbox(THEME_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); if (ImGui::Checkbox("##gs", &isThisSet)) { if (isThisSet) { SET_GS_FLAGS(selectedGsMap, setMask); @@ -848,6 +819,8 @@ void DrawFlagsTab() { SET_GS_FLAGS(selectedGsMap, currentFlagsBase & ~setMask); } } + ImGui::PopStyleVar(); + PopStyleCheckbox(); ImGui::PopID(); @@ -860,8 +833,8 @@ void DrawFlagsTab() { // GS Token Count updated, since Gold Skulltulas killed will not correlate to GS Tokens Collected. if (!(IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS) != RO_TOKENSANITY_OFF)) { static bool keepGsCountUpdated = true; - ImGui::Checkbox("Keep GS Count Updated", &keepGsCountUpdated); - UIWidgets::InsertHelpHoverText("Automatically adjust the number of gold skulltula tokens acquired based on set flags."); + Checkbox("Keep GS Count Updated", &keepGsCountUpdated, + checkboxOptionsBase.Tooltip("Automatically adjust the number of gold skulltula tokens acquired based on set flags.")); int32_t gsCount = 0; if (keepGsCountUpdated) { for (int32_t gsFlagIndex = 0; gsFlagIndex < 6; gsFlagIndex++) { @@ -870,7 +843,7 @@ void DrawFlagsTab() { gSaveContext.inventory.gsTokens = gsCount; } } - }); + }, "Gold Skulltulas"); for (int i = 0; i < flagTables.size(); i++) { const FlagTable& flagTable = flagTables[i]; @@ -899,7 +872,7 @@ void DrawFlagsTab() { DrawFlagTableArray16(flagTable, j, gSaveContext.ship.randomizerInf[j]); break; } - }); + }, flagTable.name); } // make some buttons to help with fishsanity debugging @@ -941,6 +914,8 @@ void DrawUpgrade(const std::string& categoryName, int32_t categoryId, const std: ImGui::Text("%s", categoryName.c_str()); ImGui::SameLine(); ImGui::PushID(categoryName.c_str()); + PushStyleCombobox(THEME_COLOR); + ImGui::AlignTextToFramePadding(); if (ImGui::BeginCombo("##upgrade", names[CUR_UPG_VALUE(categoryId)].c_str())) { for (int32_t i = 0; i < names.size(); i++) { if (ImGui::Selectable(names[i].c_str())) { @@ -950,8 +925,9 @@ void DrawUpgrade(const std::string& categoryName, int32_t categoryId, const std: ImGui::EndCombo(); } + PopStyleCombobox(); ImGui::PopID(); - UIWidgets::SetLastItemHoverText(categoryName.c_str()); + UIWidgets::Tooltip(categoryName.c_str()); } // Draws a combo that lets you choose and upgrade value from a popup grid of icons @@ -959,59 +935,50 @@ void DrawUpgradeIcon(const std::string& categoryName, int32_t categoryId, const static const char* upgradePopupPicker = "upgradePopupPicker"; ImGui::PushID(categoryName.c_str()); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + + PushStyleButton(Colors::DarkGray); uint8_t item = items[CUR_UPG_VALUE(categoryId)]; if (item != ITEM_NONE) { const ItemMapEntry& slotEntry = itemMapping[item]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); - if (ret) { + if (ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1))) { ImGui::OpenPopup(upgradePopupPicker); } } else { - if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##itemNone", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { ImGui::OpenPopup(upgradePopupPicker); } } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(categoryName.c_str()); + PopStyleButton(); + Tooltip(categoryName.c_str()); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); if (ImGui::BeginPopup(upgradePopupPicker)) { for (int32_t pickerIndex = 0; pickerIndex < items.size(); pickerIndex++) { if ((pickerIndex % 8) != 0) { ImGui::SameLine(); } - + + PushStyleButton(Colors::DarkGray); if (items[pickerIndex] == ITEM_NONE) { - if (ImGui::Button("##upgradePopupPicker", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##upgradePopupPicker", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { Inventory_ChangeUpgrade(categoryId, pickerIndex); ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText("None"); + Tooltip("None"); } else { const ItemMapEntry& slotEntry = itemMapping[items[pickerIndex]]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { Inventory_ChangeUpgrade(categoryId, pickerIndex); ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id)); + Tooltip(SohUtils::GetItemName(slotEntry.id).c_str()); } + PopStyleButton(); } - ImGui::EndPopup(); } - ImGui::PopStyleVar(); - ImGui::PopID(); } @@ -1035,11 +1002,9 @@ void DrawEquipmentTab() { uint32_t bitMask = 1 << i; bool hasEquip = (bitMask & gSaveContext.inventory.equipment) != 0; const ItemMapEntry& entry = itemMapping[equipmentValues[i]]; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasEquip ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasEquip) { gSaveContext.inventory.equipment &= ~bitMask; @@ -1047,9 +1012,9 @@ void DrawEquipmentTab() { gSaveContext.inventory.equipment |= bitMask; } } - ImGui::PopStyleColor(); + PopStyleButton(); + Tooltip(SohUtils::GetItemName(entry.id).c_str()); ImGui::PopID(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(entry.id)); } const std::vector bulletBagValues = { @@ -1137,11 +1102,9 @@ void DrawQuestItemButton(uint32_t item) { const QuestMapEntry& entry = questMapping[item]; uint32_t bitMask = 1 << entry.id; bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasQuestItem) { gSaveContext.inventory.questItems &= ~bitMask; @@ -1149,8 +1112,8 @@ void DrawQuestItemButton(uint32_t item) { gSaveContext.inventory.questItems |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetQuestItemName(entry.id).c_str()); } // Draws a toggleable icon for a dungeon item that is faded when disabled @@ -1158,11 +1121,9 @@ void DrawDungeonItemButton(uint32_t item, uint32_t scene) { const ItemMapEntry& entry = itemMapping[item]; uint32_t bitMask = 1 << (entry.id - ITEM_KEY_BOSS); // Bitset starts at ITEM_KEY_BOSS == 0. the rest are sequential bool hasItem = (bitMask & gSaveContext.inventory.dungeonItems[scene]) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasItem ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasItem) { gSaveContext.inventory.dungeonItems[scene] &= ~bitMask; @@ -1170,12 +1131,11 @@ void DrawDungeonItemButton(uint32_t item, uint32_t scene) { gSaveContext.inventory.dungeonItems[scene] |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetItemName(entry.id).c_str()); } void DrawQuestStatusTab() { - ImGui::PushItemWidth(ImGui::GetFontSize() * 6); for (int32_t i = QUEST_MEDALLION_FOREST; i < QUEST_MEDALLION_LIGHT + 1; i++) { if (i != QUEST_MEDALLION_FOREST) { @@ -1193,14 +1153,13 @@ void DrawQuestStatusTab() { // Put Stone of Agony and Gerudo Card on the same line with a little space between them ImGui::SameLine(); - ImGui::Dummy(ImVec2(20, 0)); + ImGui::Dummy(ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2); ImGui::SameLine(); DrawQuestItemButton(QUEST_STONE_OF_AGONY); ImGui::SameLine(); DrawQuestItemButton(QUEST_GERUDO_CARD); - for (const auto& [quest, entry] : songMapping) { if ((entry.id != QUEST_SONG_MINUET) && (entry.id != QUEST_SONG_LULLABY)) { ImGui::SameLine(); @@ -1208,11 +1167,9 @@ void DrawQuestStatusTab() { uint32_t bitMask = 1 << entry.id; bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded), - ImVec2(16.0f, 24.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(32.0f, 48.0f), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasQuestItem) { gSaveContext.inventory.questItems &= ~bitMask; @@ -1220,25 +1177,28 @@ void DrawQuestStatusTab() { gSaveContext.inventory.questItems |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetQuestItemName(entry.id).c_str()); } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("GS Count", ImGuiDataType_S16, &gSaveContext.inventory.gsTokens); - UIWidgets::InsertHelpHoverText("Number of gold skulltula tokens aquired"); + PopStyleInput(); + InsertHelpHoverText("Number of gold skulltula tokens aquired"); uint32_t bitMask = 1 << QUEST_SKULL_TOKEN; bool gsUnlocked = (bitMask & gSaveContext.inventory.questItems) != 0; - if (ImGui::Checkbox("GS unlocked", &gsUnlocked)) { + if (Checkbox("GS unlocked", &gsUnlocked, CheckboxOptions().Color(THEME_COLOR))) { if (gsUnlocked) { gSaveContext.inventory.questItems |= bitMask; } else { gSaveContext.inventory.questItems &= ~bitMask; } } - UIWidgets::InsertHelpHoverText("If unlocked, enables showing the gold skulltula count in the quest status menu"); + InsertHelpHoverText("If unlocked, enables showing the gold skulltula count in the quest status menu"); int32_t pohCount = (gSaveContext.inventory.questItems & 0xF0000000) >> 28; + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("PoH count", std::to_string(pohCount).c_str())) { for (int32_t i = 0; i < 4; i++) { if (ImGui::Selectable(std::to_string(i).c_str(), pohCount == i)) { @@ -1248,13 +1208,14 @@ void DrawQuestStatusTab() { } ImGui::EndCombo(); } - UIWidgets::InsertHelpHoverText("The number of pieces of heart acquired towards the next heart container"); + InsertHelpHoverText("The number of pieces of heart acquired towards the next heart container"); + PopStyleCombobox(); DrawGroupWithBorder([&]() { ImGui::Text("Dungeon Items"); static int32_t dungeonItemsScene = SCENE_DEKU_TREE; - ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##DungeonSelect", SohUtils::GetSceneName(dungeonItemsScene).c_str())) { for (int32_t dungeonIndex = SCENE_DEKU_TREE; dungeonIndex < SCENE_JABU_JABU_BOSS + 1; dungeonIndex++) { if (ImGui::Selectable(SohUtils::GetSceneName(dungeonIndex).c_str(), @@ -1265,7 +1226,7 @@ void DrawQuestStatusTab() { ImGui::EndCombo(); } - ImGui::PopItemWidth(); + PopStyleCombobox(); DrawDungeonItemButton(ITEM_KEY_BOSS, dungeonItemsScene); ImGui::SameLine(); @@ -1277,16 +1238,17 @@ void DrawQuestStatusTab() { float lineHeight = ImGui::GetTextLineHeightWithSpacing(); ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[ITEM_KEY_SMALL].name), ImVec2(lineHeight, lineHeight)); ImGui::SameLine(); + PushStyleInput(THEME_COLOR); if (ImGui::InputScalar("##Keys", ImGuiDataType_S8, gSaveContext.inventory.dungeonKeys + dungeonItemsScene)) { gSaveContext.ship.stats.dungeonKeys[dungeonItemsScene] = gSaveContext.inventory.dungeonKeys[dungeonItemsScene]; }; + PopStyleInput(); } else { // dungeonItems is size 20 but dungeonKeys is size 19, so there are no keys for the last scene (Barinade's Lair) ImGui::Text("Barinade's Lair does not have small keys"); } - }); + }, "Dungeon Items"); - ImGui::PopItemWidth(); } void DrawPlayerTab() { @@ -1364,50 +1326,53 @@ void DrawPlayerTab() { } ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); DrawGroupWithBorder([&]() { ImGui::Text("Link's Position"); - ImGui::InputScalar("X Pos", ImGuiDataType_Float, &player->actor.world.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("Y Pos", ImGuiDataType_Float, &player->actor.world.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("Z Pos", ImGuiDataType_Float, &player->actor.world.pos.z); - }); - + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##Pos", ImGuiDataType_Float, &player->actor.world.pos.x); + ImGui::InputScalar("Y##Pos", ImGuiDataType_Float, &player->actor.world.pos.y); + ImGui::InputScalar("Z##Pos", ImGuiDataType_Float, &player->actor.world.pos.z); + ImGui::PopItemWidth(); + }, "Link's Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Link's Rotation"); - UIWidgets::InsertHelpHoverText("For Link's rotation in relation to the world"); - ImGui::InputScalar("X Rot", ImGuiDataType_S16, &player->actor.world.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("Y Rot", ImGuiDataType_S16, &player->actor.world.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("Z Rot", ImGuiDataType_S16, &player->actor.world.rot.z); - }); - + InsertHelpHoverText("For Link's rotation in relation to the world"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##Rot", ImGuiDataType_S16, &player->actor.world.rot.x); + ImGui::InputScalar("Y##Rot", ImGuiDataType_S16, &player->actor.world.rot.y); + ImGui::InputScalar("Z##Rot", ImGuiDataType_S16, &player->actor.world.rot.z); + ImGui::PopItemWidth(); + }, "Link's Rotation"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Link's Model Rotation"); - UIWidgets::InsertHelpHoverText("For Link's actual model"); - ImGui::InputScalar("X ModRot", ImGuiDataType_S16, &player->actor.shape.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("Y ModRot", ImGuiDataType_S16, &player->actor.shape.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("Z ModRot", ImGuiDataType_S16, &player->actor.shape.rot.z); - }); + InsertHelpHoverText("For Link's actual model"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.x); + ImGui::InputScalar("Y##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.y); + ImGui::InputScalar("Z##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.z); + ImGui::PopItemWidth(); + }, "Link's Model Rotation"); ImGui::InputScalar("Linear Velocity", ImGuiDataType_Float, &player->linearVelocity); - UIWidgets::InsertHelpHoverText("Link's speed along the XZ plane"); + InsertHelpHoverText("Link's speed along the XZ plane"); ImGui::InputScalar("Y Velocity", ImGuiDataType_Float, &player->actor.velocity.y); - UIWidgets::InsertHelpHoverText("Link's speed along the Y plane. Caps at -20"); + InsertHelpHoverText("Link's speed along the Y plane. Caps at -20"); ImGui::InputScalar("Wall Height", ImGuiDataType_Float, &player->yDistToLedge); - UIWidgets::InsertHelpHoverText("Height used to determine whether Link can climb or grab a ledge at the top"); + InsertHelpHoverText("Height used to determine whether Link can climb or grab a ledge at the top"); ImGui::InputScalar("Invincibility Timer", ImGuiDataType_S8, &player->invincibilityTimer); - UIWidgets::InsertHelpHoverText("Can't take damage while this is nonzero"); + InsertHelpHoverText("Can't take damage while this is nonzero"); ImGui::InputScalar("Gravity", ImGuiDataType_Float, &player->actor.gravity); - UIWidgets::InsertHelpHoverText("Rate at which Link falls. Default -4.0f"); + InsertHelpHoverText("Rate at which Link falls. Default -4.0f"); + PopStyleInput(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Link Age on Load", gPlayState->linkAgeOnLoad == 0 ? "Adult" : "Child")) { if (ImGui::Selectable("Adult")) { gPlayState->linkAgeOnLoad = 0; @@ -1417,132 +1382,140 @@ void DrawPlayerTab() { } ImGui::EndCombo(); } - - UIWidgets::InsertHelpHoverText("This will change Link's age when you load a map"); - + InsertHelpHoverText("This will change Link's age when you load a map"); + PopStyleCombobox(); ImGui::Separator(); - ImGui::Text("Link's Current Equipment"); - ImGui::PushItemWidth(ImGui::GetFontSize() * 15); - if (ImGui::BeginCombo("Sword", curSword)) { - if (ImGui::Selectable("None")) { - player->currentSwordItemId = ITEM_NONE; - gSaveContext.equips.buttonItems[0] = ITEM_NONE; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_NONE); - } - if (ImGui::Selectable("Kokiri Sword")) { - player->currentSwordItemId = ITEM_SWORD_KOKIRI; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI); - } - if (ImGui::Selectable("Master Sword")) { - player->currentSwordItemId = ITEM_SWORD_MASTER; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); - } - if (ImGui::Selectable("Biggoron's Sword")) { - if (gSaveContext.bgsFlag) { - if (gSaveContext.swordHealth < 8) { - gSaveContext.swordHealth = 8; - } - player->currentSwordItemId = ITEM_SWORD_BGS; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_BGS; - } else { - if (gSaveContext.swordHealth < 8) { - gSaveContext.swordHealth = 8; - } - player->currentSwordItemId = ITEM_SWORD_BGS; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KNIFE; + DrawGroupWithBorder([&]() { + PushStyleCombobox(THEME_COLOR); + ImGui::Text("Link's Current Equipment"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + if (ImGui::BeginCombo("Sword", curSword)) { + if (ImGui::Selectable("None")) { + player->currentSwordItemId = ITEM_NONE; + gSaveContext.equips.buttonItems[0] = ITEM_NONE; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_NONE); } - - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_BIGGORON); - } - if (ImGui::Selectable("Fishing Pole")) { - player->currentSwordItemId = ITEM_FISHING_POLE; - gSaveContext.equips.buttonItems[0] = ITEM_FISHING_POLE; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); - } - ImGui::EndCombo(); + if (ImGui::Selectable("Kokiri Sword")) { + player->currentSwordItemId = ITEM_SWORD_KOKIRI; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI); + } + if (ImGui::Selectable("Master Sword")) { + player->currentSwordItemId = ITEM_SWORD_MASTER; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); + } + if (ImGui::Selectable("Biggoron's Sword")) { + if (gSaveContext.bgsFlag) { + if (gSaveContext.swordHealth < 8) { + gSaveContext.swordHealth = 8; + } + player->currentSwordItemId = ITEM_SWORD_BGS; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_BGS; + } + else { + if (gSaveContext.swordHealth < 8) { + gSaveContext.swordHealth = 8; + } + player->currentSwordItemId = ITEM_SWORD_BGS; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KNIFE; + } - } - if (ImGui::BeginCombo("Shield", curShield)) { - if (ImGui::Selectable("None")) { - player->currentShield = PLAYER_SHIELD_NONE; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_NONE); - } - if (ImGui::Selectable("Deku Shield")) { - player->currentShield = PLAYER_SHIELD_DEKU; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU); - } - if (ImGui::Selectable("Hylian Shield")) { - player->currentShield = PLAYER_SHIELD_HYLIAN; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_HYLIAN); - } - if (ImGui::Selectable("Mirror Shield")) { - player->currentShield = PLAYER_SHIELD_MIRROR; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR); - } - ImGui::EndCombo(); - } + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_BIGGORON); + } + if (ImGui::Selectable("Fishing Pole")) { + player->currentSwordItemId = ITEM_FISHING_POLE; + gSaveContext.equips.buttonItems[0] = ITEM_FISHING_POLE; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); + } + ImGui::EndCombo(); - if (ImGui::BeginCombo("Tunic", curTunic)) { - if (ImGui::Selectable("Kokiri Tunic")) { - player->currentTunic = PLAYER_TUNIC_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); } - if (ImGui::Selectable("Goron Tunic")) { - player->currentTunic = PLAYER_TUNIC_GORON; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON); + if (ImGui::BeginCombo("Shield", curShield)) { + if (ImGui::Selectable("None")) { + player->currentShield = PLAYER_SHIELD_NONE; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_NONE); + } + if (ImGui::Selectable("Deku Shield")) { + player->currentShield = PLAYER_SHIELD_DEKU; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU); + } + if (ImGui::Selectable("Hylian Shield")) { + player->currentShield = PLAYER_SHIELD_HYLIAN; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_HYLIAN); + } + if (ImGui::Selectable("Mirror Shield")) { + player->currentShield = PLAYER_SHIELD_MIRROR; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Zora Tunic")) { - player->currentTunic = PLAYER_TUNIC_ZORA; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_ZORA); - } - ImGui::EndCombo(); - } - if (ImGui::BeginCombo("Boots", curBoots)) { - if (ImGui::Selectable("Kokiri Boots")) { - player->currentBoots = PLAYER_BOOTS_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); + if (ImGui::BeginCombo("Tunic", curTunic)) { + if (ImGui::Selectable("Kokiri Tunic")) { + player->currentTunic = PLAYER_TUNIC_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); + } + if (ImGui::Selectable("Goron Tunic")) { + player->currentTunic = PLAYER_TUNIC_GORON; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON); + } + if (ImGui::Selectable("Zora Tunic")) { + player->currentTunic = PLAYER_TUNIC_ZORA; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_ZORA); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Iron Boots")) { - player->currentBoots = PLAYER_BOOTS_IRON; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_IRON); + + if (ImGui::BeginCombo("Boots", curBoots)) { + if (ImGui::Selectable("Kokiri Boots")) { + player->currentBoots = PLAYER_BOOTS_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); + } + if (ImGui::Selectable("Iron Boots")) { + player->currentBoots = PLAYER_BOOTS_IRON; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_IRON); + } + if (ImGui::Selectable("Hover Boots")) { + player->currentBoots = PLAYER_BOOTS_HOVER; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_HOVER); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Hover Boots")) { - player->currentBoots = PLAYER_BOOTS_HOVER; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_HOVER); - } - ImGui::EndCombo(); - } + ImGui::PopItemWidth(); + PopStyleCombobox(); + }, "Current Equipment"); + ImGui::SameLine(); ImU16 one = 1; - ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { - ImGui::Text("Current B Item"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); + ImGui::Text("Current Items"); ImGui::InputScalar("B Button", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[0], &one, NULL); - ImGui::NewLine(); - - ImGui::Text("Current C Equips"); ImGui::InputScalar("C Left", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[1], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("C Down", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[2], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("C Right", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[3], &one, NULL); - - if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { - ImGui::NewLine(); - ImGui::Text("Current D-pad Equips"); - ImGui::InputScalar("D-pad Up ", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[4], &one, NULL); // Two spaces at the end for aligning, not elegant but it's working - ImGui::SameLine(); + PopStyleInput(); + ImGui::PopItemWidth(); + }, "Current Items"); + + if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { + ImGui::SameLine(); + DrawGroupWithBorder([&]() { + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); + ImGui::Text("Current D-pad Items"); + // Two spaces at the end for aligning, not elegant but it's working + ImGui::InputScalar("D-pad Up ", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[4], &one, NULL); ImGui::InputScalar("D-pad Down", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[5], &one, NULL); - // Intentionnal to not put everything on the same line, else it's taking too much for lower resolution. ImGui::InputScalar("D-pad Left", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[6], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("D-pad Right", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[7], &one, NULL); - } - }); + PopStyleInput(); + ImGui::PopItemWidth(); + }, "Current D-pad Items"); + } ImGui::Text("Player State"); uint8_t bit[32] = {}; @@ -1550,8 +1523,9 @@ void DrawPlayerTab() { std::vector> flag_strs = { state1, state2, state3 }; for (int j = 0; j <= 2; j++) { + std::string label = fmt::format("State Flags {}", j + 1); DrawGroupWithBorder([&]() { - ImGui::Text("State Flags %d", j + 1); + ImGui::Text("%s", label.c_str()); std::vector state = flag_strs[j]; for (int i = 0; i <= 31; i++) { bit[i] = ((flags[j] >> i) & 1); @@ -1559,46 +1533,60 @@ void DrawPlayerTab() { ImGui::Text("%s", state[i].c_str()); } } - }); + }, label.c_str()); ImGui::SameLine(); } DrawGroupWithBorder([&]() { ImGui::Text("Sword"); ImGui::Text(" %d", player->meleeWeaponState); - }); + }, "Sword"); } else { ImGui::Text("Global Context needed for player info!"); } } +void ResetBaseOptions() { + intSliderOptionsBase.Color(THEME_COLOR).Size({320.0f, 0.0f}).Tooltip(""); + buttonOptionsBase.Color(THEME_COLOR).Size(Sizes::Inline).Tooltip(""); + checkboxOptionsBase.Color(THEME_COLOR).Tooltip(""); + comboboxOptionsBase.Color(THEME_COLOR).ComponentAlignment(ComponentAlignment::Left).LabelPosition(LabelPosition::Near).Tooltip(""); +} + void SaveEditorWindow::DrawElement() { + PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + ResetBaseOptions(); if (ImGui::BeginTabItem("Info")) { DrawInfoTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Inventory")) { DrawInventoryTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Flags")) { DrawFlagsTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Equipment")) { DrawEquipmentTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Quest Status")) { DrawQuestStatusTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Player")) { DrawPlayerTab(); ImGui::EndTabItem(); @@ -1606,6 +1594,7 @@ void SaveEditorWindow::DrawElement() { ImGui::EndTabBar(); } + PopStyleTabs(); } void SaveEditorWindow::InitElement() {} diff --git a/soh/soh/Enhancements/debugger/dlViewer.cpp b/soh/soh/Enhancements/debugger/dlViewer.cpp index 4f4d13bff..1459d2809 100644 --- a/soh/soh/Enhancements/debugger/dlViewer.cpp +++ b/soh/soh/Enhancements/debugger/dlViewer.cpp @@ -1,6 +1,7 @@ #include "actorViewer.h" #include "soh/util.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "ResourceManager.h" #include "DisplayList.h" #include "soh/OTRGlobals.h" @@ -90,11 +91,14 @@ void PerformDisplayListSearch() { } void DLViewerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); // Debounce the search field as listing otr files is expensive + UIWidgets::PushStyleInput(THEME_COLOR); if (ImGui::InputText("Search Display Lists", searchString, ARRAY_COUNT(searchString))) { doSearch = true; searchDebounceFrames = 30; } + UIWidgets::PopStyleInput(); if (doSearch) { if (searchDebounceFrames == 0) { @@ -105,6 +109,7 @@ void DLViewerWindow::DrawElement() { searchDebounceFrames--; } + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Active Display List", activeDisplayList.c_str())) { for (size_t i = 0; i < displayListSearchResults.size(); i++) { if (ImGui::Selectable(displayListSearchResults[i].c_str())) { @@ -114,8 +119,10 @@ void DLViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); if (activeDisplayList == "") { + ImGui::PopFont(); return; } @@ -124,6 +131,7 @@ void DLViewerWindow::DrawElement() { if (res->GetInitData()->Type != static_cast(Fast::ResourceType::DisplayList)) { ImGui::Text("Resource type is not a Display List. Please choose another."); + ImGui::PopFont(); return; } @@ -144,6 +152,7 @@ void DLViewerWindow::DrawElement() { ImGui::SameLine(); ImGui::PushItemWidth(175.0f); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo(("CMD" + id).c_str(), cmdLabel.c_str())) { if (ImGui::Selectable("gsDPSetPrimColor") && cmd != G_SETPRIMCOLOR) { *gfx = gsDPSetPrimColor(0, 0, 0, 0, 0, 255); @@ -162,6 +171,7 @@ void DLViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::PopItemWidth(); @@ -194,9 +204,11 @@ void DLViewerWindow::DrawElement() { if (cmd == G_SETGRAYSCALE) { bool* state = (bool*)&gfx->words.w1; ImGui::SameLine(); + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (ImGui::Checkbox(("state" + id).c_str(), state)) { // } + UIWidgets::PopStyleCheckbox(); } if (cmd == G_SETTILE) { ImGui::SameLine(); @@ -317,8 +329,10 @@ void DLViewerWindow::DrawElement() { } } catch (const std::exception& e) { ImGui::Text("Error displaying DL instructions."); + ImGui::PopFont(); return; } + ImGui::PopFont(); } void DLViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/valueViewer.cpp b/soh/soh/Enhancements/debugger/valueViewer.cpp index 7d09dfb63..40ec77f71 100644 --- a/soh/soh/Enhancements/debugger/valueViewer.cpp +++ b/soh/soh/Enhancements/debugger/valueViewer.cpp @@ -1,5 +1,6 @@ #include "valueViewer.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" #include "soh/ShipInit.hpp" @@ -142,13 +143,15 @@ void RegisterValueViewerHooks() { RegisterShipInitFunc initFunc(RegisterValueViewerHooks, { CVAR_NAME }); void ValueViewerWindow::DrawElement() { - UIWidgets::PaddedEnhancementCheckbox("Enable Printing", CVAR_NAME); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + UIWidgets::CVarCheckbox("Enable Printing", CVAR_NAME, UIWidgets::CheckboxOptions().Color(THEME_COLOR)); ImGui::BeginGroup(); static int selectedElement = -1; std::string selectedElementText = (selectedElement == -1) ? "Select a value" : ( std::string(valueTable[selectedElement].name) + " (" + std::string(valueTable[selectedElement].path) + ")" ); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##valueViewerElement", selectedElementText.c_str())) { for (int i = 0; i < valueTable.size(); i++) { if (valueTable[i].isActive) continue; @@ -165,20 +168,28 @@ void ValueViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::SameLine(); + UIWidgets::PushStyleButton(THEME_COLOR); if (selectedElement != -1 && ImGui::Button("+")) { valueTable[selectedElement].isActive = true; selectedElement = -1; } + UIWidgets::PopStyleButton(); ImGui::EndGroup(); for (int i = 0; i < valueTable.size(); i++) { ValueTableElement& element = valueTable[i]; if (!element.isActive || (gPlayState == NULL && element.requiresPlayState)) continue; - if (ImGui::Button(("x##" + std::string(element.name)).c_str())) { + UIWidgets::PushStyleButton(THEME_COLOR); + UIWidgets::PushStyleCheckbox(THEME_COLOR); + ImGui::AlignTextToFramePadding(); + if (ImGui::Button((ICON_FA_TIMES + std::string("##") + std::string(element.name)).c_str())) { element.isActive = false; element.isPrinted = false; } + UIWidgets::PopStyleCheckbox(); + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s:", element.name); ImGui::SameLine(); @@ -212,7 +223,7 @@ void ValueViewerWindow::DrawElement() { break; } ImGui::SameLine(); - + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (element.type <= TYPE_U32) { ImGui::Checkbox(("Hex##" + std::string(element.name)).c_str(), &element.typeFormat); ImGui::SameLine(); @@ -220,23 +231,30 @@ void ValueViewerWindow::DrawElement() { ImGui::Checkbox(("Trim##" + std::string(element.name)).c_str(), &element.typeFormat); ImGui::SameLine(); } + UIWidgets::PopStyleCheckbox(); ImGui::BeginGroup(); if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ValueViewerEnablePrinting"), 0)) { + UIWidgets::PushStyleCheckbox(THEME_COLOR); ImGui::Checkbox(("Print##" + std::string(element.name)).c_str(), &element.isPrinted); + UIWidgets::PopStyleCheckbox(); if (element.isPrinted) { char* prefix = (char*)element.prefix.c_str(); ImGui::SameLine(); ImGui::SetNextItemWidth(80.0f); + UIWidgets::PushStyleInput(THEME_COLOR); if (ImGui::InputText(("Prefix##" + std::string(element.name)).c_str(), prefix, 10)) { element.prefix = prefix; } + UIWidgets::PopStyleInput(); ImGui::SameLine(); ImGui::ColorEdit3(("##color" + std::string(element.name)).c_str(), (float*)&element.color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); ImGui::SameLine(); + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (ImGui::Button(("Position##" + std::string(element.name)).c_str())) { ImGui::OpenPopup(("Position Picker##" + std::string(element.name)).c_str()); } + UIWidgets::PopStyleCheckbox(); if (ImGui::BeginPopup(("Position Picker##" + std::string(element.name)).c_str())) { ImGui::DragInt("X", (int*)&element.x, 1.0f, 0, 44); ImGui::DragInt("Y", (int*)&element.y, 1.0f, 0, 29); @@ -246,6 +264,7 @@ void ValueViewerWindow::DrawElement() { } ImGui::EndGroup(); } + ImGui::PopFont(); } void ValueViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index db96a7700..14228054b 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -39,7 +39,7 @@ typedef enum { MIRRORED_WORLD_ALWAYS, MIRRORED_WORLD_RANDOM, MIRRORED_WORLD_RANDOM_SEEDED, - MIRRORED_WORLD_DUNGEONS_All, + MIRRORED_WORLD_DUNGEONS_ALL, MIRRORED_WORLD_DUNGEONS_VANILLA, MIRRORED_WORLD_DUNGEONS_MQ, MIRRORED_WORLD_DUNGEONS_RANDOM, @@ -75,6 +75,18 @@ typedef enum { BONK_DAMAGE_OHKO, } BonkDamage; +typedef enum { + DAMAGE_VANILLA, + DAMAGE_DOUBLE, + DAMAGE_QUADRUPLE, + DAMAGE_OCTUPLE, + DAMAGE_FOOLISH, + DAMAGE_RIDICULOUS, + DAMAGE_MERCILESS, + DAMAGE_TORTURE, + DAMAGE_OHKO +} DamageMultType; + typedef enum { DEKU_STICK_NORMAL, DEKU_STICK_UNBREAKABLE, @@ -87,4 +99,16 @@ typedef enum { SWORD_TOGGLE_BOTH_AGES, } SwordToggleMode; +typedef enum { + TIME_TRAVEL_DISABLED, + TIME_TRAVEL_OOT, + TIME_TRAVEL_ANY +} TimeTravelType; + +typedef enum { + WATERFALL_ALWAYS, + WATERFALL_ONCE, + WATERFALL_NEVER +} SleepingWaterfallType; + #endif diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index aff0d0726..af7807b3a 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -1,11 +1,11 @@ #include "gameplaystats.h" -#include "gameplaystatswindow.h" #include "soh/SaveManager.h" #include "functions.h" #include "macros.h" #include "soh/cvar_prefixes.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/util.h" #include @@ -376,14 +376,18 @@ void SaveStats(SaveContext* saveContext, int sectionID, bool fullSave) { }); } -void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE) { +void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE, + const char* tooltip = "") { ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", label); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(value.c_str()).x - 8.0f)); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(value.c_str()).x)); ImGui::Text("%s", value.c_str()); ImGui::PopStyleColor(); + if (tooltip != "" && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } } bool compareTimestampInfoByTime(const TimestampInfo& a, const TimestampInfo& b) { @@ -527,8 +531,8 @@ void DrawGameplayStatsCountsTab() { } } } - GameplayStatsRow("Rupees Collected:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_COLLECTED])); - UIWidgets::Tooltip("Includes rupees collected with a full wallet."); + GameplayStatsRow("Rupees Collected:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_COLLECTED]), + COLOR_WHITE, "Includes rupees collected with a full wallet."); GameplayStatsRow("Rupees Spent:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_SPENT])); GameplayStatsRow("Chests Opened:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_CHESTS_OPENED])); GameplayStatsRow("Ammo Used:", formatIntGameplayStat(ammoUsed)); @@ -606,26 +610,34 @@ void DrawGameplayStatsBreakdownTab() { } void DrawGameplayStatsOptionsTab() { - UIWidgets::PaddedEnhancementCheckbox("Show in-game total timer", CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"), true, false); - UIWidgets::InsertHelpHoverText("Keep track of the timer as an in-game HUD element. The position of the timer can be changed in the Cosmetics Editor."); - UIWidgets::PaddedEnhancementCheckbox("Show latest timestamps on top", CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), true, false); - ImGui::SameLine(); - UIWidgets::InsertHelpHoverText("Allows a more in-depth perspective of time spent in a certain map."); - UIWidgets::PaddedEnhancementCheckbox("RTA Timing on new files", CVAR_ENHANCEMENT("GameplayStats.RTATiming"), true, false); - ImGui::SameLine(); - UIWidgets::InsertHelpHoverText( - "Timestamps are relative to starting timestamp rather than in game time, usually necessary for races/speedruns.\n\n" - "Starting timestamp is on first non-c-up input after intro cutscene.\n\n" - "NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT" - ); - UIWidgets::PaddedEnhancementCheckbox("Show additional detail timers", CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Show Debug Info", CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo")); + UIWidgets::CVarCheckbox("Show in-game total timer", CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"), + UIWidgets::CheckboxOptions() + .Tooltip("Keep track of the timer as an in-game HUD element. The position of the " + "timer can be changed in the Cosmetics Editor.") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show latest timestamps on top", CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Room Breakdown", CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), + UIWidgets::CheckboxOptions() + .Tooltip("Allows a more in-depth perspective of time spent in a certain map.") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("RTA Timing on new files", CVAR_ENHANCEMENT("GameplayStats.RTATiming"), + UIWidgets::CheckboxOptions() + .Tooltip("Timestamps are relative to starting timestamp rather than in game time, " + "usually necessary for races/speedruns.\n\n" + "Starting timestamp is on first non-c-up input after intro cutscene.\n\n" + "NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show additional detail timers", CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show Debug Info", CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); } void GameplayStatsWindow::DrawElement() { DrawGameplayStatsHeader(); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Timestamps")) { DrawGameplayStatsTimestampsTab(); @@ -645,6 +657,7 @@ void GameplayStatsWindow::DrawElement() { } ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving."); } diff --git a/soh/soh/Enhancements/gameplaystatswindow.h b/soh/soh/Enhancements/gameplaystatswindow.h index 453b24559..446cc1ef4 100644 --- a/soh/soh/Enhancements/gameplaystatswindow.h +++ b/soh/soh/Enhancements/gameplaystatswindow.h @@ -1,5 +1,4 @@ #include -#include "gameplaystats.h" class GameplayStatsWindow : public Ship::GuiWindow { public: diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index cd60f7d8c..cfcb98fa2 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -463,7 +463,7 @@ void UpdateMirrorModeState(int32_t sceneNum) { mirroredMode == MIRRORED_WORLD_ALWAYS || ((mirroredMode == MIRRORED_WORLD_RANDOM || mirroredMode == MIRRORED_WORLD_RANDOM_SEEDED) && randomMirror) || // Dungeon modes - (inDungeon && (mirroredMode == MIRRORED_WORLD_DUNGEONS_All || + (inDungeon && (mirroredMode == MIRRORED_WORLD_DUNGEONS_ALL || (mirroredMode == MIRRORED_WORLD_DUNGEONS_VANILLA && !ResourceMgr_IsSceneMasterQuest(sceneNum)) || (mirroredMode == MIRRORED_WORLD_DUNGEONS_MQ && ResourceMgr_IsSceneMasterQuest(sceneNum)) || ((mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM || mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED) && randomMirror))) diff --git a/soh/soh/Enhancements/presets.cpp b/soh/soh/Enhancements/presets.cpp index 2b1812d21..240d0fa81 100644 --- a/soh/soh/Enhancements/presets.cpp +++ b/soh/soh/Enhancements/presets.cpp @@ -3,8 +3,10 @@ #include #include #include -#include "soh/SohGui/UIWidgets.hpp" #include +#include "soh/SohGui/MenuTypes.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" std::string FormatLocations(std::vector locs) { std::string locString = ""; @@ -30,6 +32,7 @@ void applyPreset(std::vector entries) { CVarSetString(cvar, std::get(value).c_str()); break; } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } @@ -47,21 +50,22 @@ void DrawPresetSelector(PresetType presetTypeId) { comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); } - UIWidgets::PaddedText("Presets", false, true); + ImGui::Text("Presets", false, true); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##PresetsComboBox", selectedPresetDef.label)) { for ( auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter ) { if (ImGui::Selectable(iter->second.label, iter->first == selectedPresetId)) { CVarSetInteger(presetTypeCvar.c_str(), iter->first); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); UIWidgets::Tooltip(comboboxTooltip.c_str()); - UIWidgets::Spacer(0); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f)); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(("Apply Preset##" + presetTypeCvar).c_str())) { for(const char* block : presetTypeDef.blocksToClear) { CVarClearBlock(block); @@ -75,5 +79,5 @@ void DrawPresetSelector(PresetType presetTypeId) { Rando::Settings::GetInstance()->ReloadOptions(); } } - ImGui::PopStyleVar(1); + UIWidgets::PopStyleButton(); } diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index e6c9399b5..4741d9d0c 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -1,4 +1,5 @@ #include "Plandomizer.h" +#include #include "soh/SohGui/UIWidgets.hpp" #include "soh/util.h" #include @@ -836,21 +837,9 @@ void PlandomizerDrawItemSlots(uint32_t index) { void PlandomizerDrawShopSlider(uint32_t index) { ImGui::PushID(index); - ImGui::Text("Price:"); - ImGui::SameLine(); - std::string MinusBTNName = " - ##Price"; - if (ImGui::Button(MinusBTNName.c_str()) && plandoLogData[index].shopPrice > 0) { - plandoLogData[index].shopPrice--; - } - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 40.0f); - ImGui::SliderInt("", &plandoLogData[index].shopPrice, 0, 999, "%d Rupees"); - ImGui::PopItemWidth(); - ImGui::SameLine(); - std::string PlusBTNName = " + ##Price"; - if (ImGui::Button(PlusBTNName.c_str()) && plandoLogData[index].shopPrice < 999) { - plandoLogData[index].shopPrice++; - } + UIWidgets::SliderInt("Price:", &plandoLogData[index].shopPrice, UIWidgets::IntSliderOptions() + .Color(THEME_COLOR).Format("%d Rupees").Min(0).Max(999).LabelPosition(UIWidgets::LabelPosition::Near) + .ComponentAlignment(UIWidgets::ComponentAlignment::Right).Size(UIWidgets::Sizes::Inline)); ImGui::PopID(); } @@ -883,13 +872,13 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { ImGui::SameLine(); if (plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_NONE && plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_SOLD_OUT) { - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { plandoLogData[index].iceTrapName = GetIceTrapName(plandoLogData[index].iceTrapModel.GetRandomizerGet()).GetForLanguage(CVarGetInteger(CVAR_SETTING("Languages"), 0)).c_str(); } ImGui::SameLine(); } - if (UIWidgets::InputString("##TrapName", &trapTextInput)) { + if (UIWidgets::InputString("##TrapName", &trapTextInput, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None))) { plandoLogData[index].iceTrapName = trapTextInput.c_str(); } @@ -900,39 +889,64 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { ImGui::PopID(); } - +static std::unordered_map rcAreaNameMap = { + { RCAREA_KOKIRI_FOREST, "Kokiri Forest" }, + { RCAREA_LOST_WOODS, "Lost Woods" }, + { RCAREA_SACRED_FOREST_MEADOW, "Sacred Forest Meadow" }, + { RCAREA_HYRULE_FIELD, "Hyrule Field" }, + { RCAREA_LAKE_HYLIA, "Lake Hylia" }, + { RCAREA_GERUDO_VALLEY, "Gerudo Valley" }, + { RCAREA_GERUDO_FORTRESS, "Gerudo Fortress" }, + { RCAREA_WASTELAND, "Haunted Wasteland" }, + { RCAREA_DESERT_COLOSSUS, "Desert Colossus" }, + { RCAREA_MARKET, "Hyrule Market" }, + { RCAREA_HYRULE_CASTLE, "Hyrule Castle" }, + { RCAREA_KAKARIKO_VILLAGE, "Kakariko Village" }, + { RCAREA_GRAVEYARD, "Graveyard" }, + { RCAREA_DEATH_MOUNTAIN_TRAIL, "Death Mountain Trail" }, + { RCAREA_GORON_CITY, "Goron City" }, + { RCAREA_DEATH_MOUNTAIN_CRATER, "Death Mountain Crater" }, + { RCAREA_ZORAS_RIVER, "Zora's River" }, + { RCAREA_ZORAS_DOMAIN, "Zora's Domain" }, + { RCAREA_ZORAS_FOUNTAIN, "Zora's Fountain" }, + { RCAREA_LON_LON_RANCH, "Lon Lon Ranch" }, + { RCAREA_DEKU_TREE, "Deku Tree" }, + { RCAREA_DODONGOS_CAVERN, "Dodongo's Cavern" }, + { RCAREA_JABU_JABUS_BELLY, "Jabu Jabu's Belly" }, + { RCAREA_FOREST_TEMPLE, "Forest Temple" }, + { RCAREA_FIRE_TEMPLE, "Fire Temple" }, + { RCAREA_WATER_TEMPLE, "Water Temple" }, + { RCAREA_SPIRIT_TEMPLE, "Spirit Temple" }, + { RCAREA_SHADOW_TEMPLE, "Shadow Temple" }, + { RCAREA_BOTTOM_OF_THE_WELL, "Bottom of the Well" }, + { RCAREA_ICE_CAVERN, "Ice Cavern" }, + { RCAREA_GERUDO_TRAINING_GROUND, "Gerudo Training Ground" }, + { RCAREA_GANONS_CASTLE, "Ganon's Castle" }, + { RCAREA_INVALID, "All" }, +}; void PlandomizerDrawOptions() { if (ImGui::BeginTable("LoadSpoiler", 2)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextColumn(); ImGui::SeparatorText("Load/Save Spoiler Log"); PlandomizerPopulateSeedList(); static size_t selectedList = 0; if (existingSeedList.size() != 0) { - if (ImGui::BeginCombo("##JsonFiles", existingSeedList[selectedList].c_str())) { - for (size_t i = 0; i < existingSeedList.size(); i++) { - bool isSelected = (selectedList == i); - if (ImGui::Selectable(existingSeedList[i].c_str(), isSelected)) { - selectedList = i; - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } + UIWidgets::Combobox("##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None)); } else { ImGui::Text("No Spoiler Logs found."); } ImGui::BeginDisabled(existingSeedList.empty()); - if (ImGui::Button("Load")) { + if (UIWidgets::Button("Load", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { logTemp = existingSeedList[selectedList].c_str(); PlandomizerLoadSpoilerLog(logTemp.c_str()); } ImGui::EndDisabled(); ImGui::BeginDisabled(spoilerLogData.empty()); ImGui::SameLine(); - if (ImGui::Button("Save")) { + if (UIWidgets::Button("Save", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerSaveSpoilerLog(); } ImGui::EndDisabled(); @@ -1000,44 +1014,19 @@ void PlandomizerDrawOptions() { } if (getTabID == TAB_HINTS) { - if (ImGui::Button("Clear All Hints")) { + if (UIWidgets::Button("Clear All Hints", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerRemoveAllHints(); } ImGui::SameLine(); - if (ImGui::Button("Randomize All Hints")) { + if (UIWidgets::Button("Randomize All Hints", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerRandomizeHint(HINT_ALL, 0); } } if (getTabID == TAB_LOCATIONS) { if (plandoLogData.size() > 0) { - const char* comboLabel = rcAreaNames[selectedArea].c_str(); - if (selectedArea == RCAREA_INVALID) { - comboLabel = "All"; - } - ImGui::Text("Filter by Area:"); + UIWidgets::Combobox("Filter by Area:##AreaFilter", &selectedArea, rcAreaNameMap, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); ImGui::SameLine(); - ImGui::PushItemWidth(300.0f); - if (ImGui::BeginCombo("##AreaFilter", comboLabel)) { - for (const auto& [area, name] : rcAreaNames) { - bool isSelected = (selectedArea == area); - - const char* displayName = name.c_str(); - if (area == RCAREA_INVALID) { - displayName = "All"; - } - if (ImGui::Selectable(displayName, isSelected)) { - selectedArea = area; - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - - ImGui::SameLine(); - if (ImGui::Button("Empty All Rewards")) { + if (UIWidgets::Button("Empty All Rewards", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { PlandomizerRemoveAllItems(); } } @@ -1067,16 +1056,14 @@ void PlandomizerDrawHintsWindow() { } ImGui::Text("New Hint: "); ImGui::SameLine(); - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions().Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)).Size(UIWidgets::Sizes::Inline).Tooltip("Randomize Hint"))) { PlandomizerRandomizeHint(HINT_SINGLE, index); } - UIWidgets::Tooltip("Randomize Hint"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 10); - if (UIWidgets::InputString("##HintMessage", &hintInputText)) { + if (UIWidgets::InputString("##HintMessage", &hintInputText, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None).Tooltip(plandomizerHintsTooltip().c_str()))) { plandoHintData[index].hintText = hintInputText.c_str(); } - UIWidgets::Tooltip(plandomizerHintsTooltip().c_str()); index++; ImGui::PopID(); } @@ -1133,6 +1120,7 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) { void PlandomizerDrawSpoilerTable() { ImGui::BeginChild("Main"); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Check Tabs")) { if (ImGui::BeginTabItem("Gossip Stones")) { getTabID = TAB_HINTS; @@ -1146,12 +1134,13 @@ void PlandomizerDrawSpoilerTable() { } } ImGui::EndTabBar(); + UIWidgets::PopStyleTabs(); ImGui::EndChild(); } void PlandomizerWindow::DrawElement() { PlandomizerDrawOptions(); - UIWidgets::PaddedSeparator(); + UIWidgets::Separator(true, true, 0.f, 0.f); PlandomizerDrawSpoilerTable(); } diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.h b/soh/soh/Enhancements/randomizer/Plandomizer.h index d337e4da6..1b7996ce4 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.h +++ b/soh/soh/Enhancements/randomizer/Plandomizer.h @@ -39,14 +39,14 @@ typedef struct { std::string hintText; } SpoilerHintObject; -typedef enum { +typedef enum PlandoTabs { TAB_HINTS, TAB_LOCATIONS -}; +} PlandoTabs; -typedef enum { +typedef enum PlandoHints { HINT_SINGLE, HINT_ALL, -}; +} PlandoHints; #endif \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index e032d1a2d..2c478e34c 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -2,7 +2,9 @@ #include "libultraship/bridge.h" #include #include +#include "soh/SohGui/SohGui.hpp" #include "soh/SohGui/UIWidgets.hpp" +#include namespace Rando { Option Option::Bool(RandomizerSettingKey key_, std::string name_, std::vector options_, @@ -130,11 +132,10 @@ void Option::Enable() { disabled = false; } -void Option::Disable(std::string text, const UIWidgets::CheckboxGraphics graphic) { - if (!disabled || disabledText != text || disabledGraphic != graphic) { +void Option::Disable(std::string text) { + if (!disabled || disabledText != text) { disabled = true; disabledText = std::move(text); - disabledGraphic = graphic; } } @@ -149,9 +150,6 @@ bool Option::RenderImGui() { case WidgetType::Checkbox: changed = RenderCheckbox(); break; - case WidgetType::TristateCheckbox: - changed = RenderTristateCheckbox(); - break; case WidgetType::Combobox: changed = RenderCombobox(); break; @@ -159,7 +157,6 @@ bool Option::RenderImGui() { changed = RenderSlider(); break; } - UIWidgets::Spacer(0); ImGui::EndGroup(); return changed; } @@ -213,50 +210,19 @@ Option::Option(size_t key_, std::string name_, std::vector options_ bool Option::RenderCheckbox() { bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } bool val = static_cast(CVarGetInteger(cvarName.c_str(), defaultOption)); - if (CustomCheckbox(name.c_str(), &val, disabled, disabledGraphic)) { + UIWidgets::CheckboxOptions widgetOptions = static_cast(UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip(description.c_str())); + widgetOptions.disabled = disabled; + if (UIWidgets::Checkbox(name.c_str(), &val, widgetOptions)) { CVarSetInteger(cvarName.c_str(), val); changed = true; Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } - return changed; -} - -bool Option::RenderTristateCheckbox() { - bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - int val = CVarGetInteger(cvarName.c_str(), defaultOption); - if (CustomCheckboxTristate(name.c_str(), &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName.c_str(), val); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } return changed; } bool Option::RenderCombobox() { bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - ImGui::Text("%s", name.c_str()); uint8_t selected = CVarGetInteger(cvarName.c_str(), defaultOption); if (selected >= options.size()) { selected = options.size(); @@ -264,25 +230,15 @@ bool Option::RenderCombobox() { changed = true; Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); + UIWidgets::ComboboxOptions widgetOptions = UIWidgets::ComboboxOptions().Color(THEME_COLOR).Tooltip(description.c_str()); + if (this->GetKey() == RSK_LOGIC_RULES) { + widgetOptions = widgetOptions.LabelPosition(UIWidgets::LabelPosition::None).ComponentAlignment(UIWidgets::ComponentAlignment::Right); } - const std::string comboName = std::string("##") + std::string(cvarName); - if (ImGui::BeginCombo(comboName.c_str(), options[selected].c_str())) { - for (size_t i = 0; i < options.size(); i++) { - if (!options[i].empty()) { - if (ImGui::Selectable(options[i].c_str(), i == selected)) { - CVarSetInteger(cvarName.c_str(), static_cast(i)); - changed = true; - selected = i; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - } - ImGui::EndCombo(); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); + widgetOptions.disabled = disabled; + if(UIWidgets::Combobox(name.c_str(), &selected, options, widgetOptions)) { + CVarSetInteger(cvarName.c_str(), static_cast(selected)); + changed = true; + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } return changed; } @@ -295,40 +251,11 @@ bool Option::RenderSlider() { CVarSetInteger(cvarName.c_str(), val); changed = true; } - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - const std::string formatName = name + ": %s"; - ImGui::Text(formatName.c_str(), options[val].c_str()); - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - UIWidgets::Spacer(0); - ImGui::BeginGroup(); - const std::string MinusBTNName = " - ##" + cvarName; - if (ImGui::Button(MinusBTNName.c_str())) { - val--; + UIWidgets::IntSliderOptions widgetOptions = UIWidgets::IntSliderOptions().Color(THEME_COLOR).Min(0).Max(options.size() - 1).Tooltip(description.c_str()).Format(options[val].c_str()).DefaultValue(defaultOption); + widgetOptions.disabled = disabled; + if (UIWidgets::SliderInt(name.c_str(), &val, widgetOptions)) { changed = true; } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - ImGui::PushItemWidth(std::min(ImGui::GetContentRegionAvail().x - 30.0f, 260.0f)); - const std::string id = "##Slider" + cvarName; - if (ImGui::SliderInt(id.c_str(), &val, 0, static_cast(options.size()) - 1, "", ImGuiSliderFlags_AlwaysClamp)) { - changed = true; - } - ImGui::PopItemWidth(); - const std::string PlusBTNName = " + ##" + cvarName; - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val++; - changed = true; - } - ImGui::EndGroup(); - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } if (val < 0) { val = 0; changed = true; @@ -465,7 +392,7 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) ImGui::TableSetColumnIndex(i); ImGui::TableHeader(mSubGroups[i]->GetName().c_str()); if (!mSubGroups[i]->GetDescription().empty()) { - UIWidgets::SetLastItemHoverText(mSubGroups[i]->GetDescription().c_str()); + UIWidgets::Tooltip(mSubGroups[i]->GetDescription().c_str()); } } ImGui::PopItemFlag(); @@ -473,12 +400,10 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) } } if (mContainerType == WidgetContainerType::SECTION && !mName.empty()) { - UIWidgets::PaddedSeparator(); - ImGui::Text("%s", mName.c_str()); + ImGui::SeparatorText(mName.c_str()); if (!mDescription.empty()) { - UIWidgets::InsertHelpHoverText(mDescription.c_str()); + UIWidgets::Tooltip(mDescription.c_str()); } - UIWidgets::PaddedSeparator(); } if (mContainerType == WidgetContainerType::COLUMN) { ImGui::TableNextColumn(); @@ -507,9 +432,6 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) if (option->HasFlag(IMFLAG_UNINDENT)) { ImGui::Unindent(); } - if (option->HasFlag(IMFLAG_SEPARATOR_BOTTOM)) { - UIWidgets::PaddedSeparator(false, true); - } } } if (mContainerType == WidgetContainerType::COLUMN) { diff --git a/soh/soh/Enhancements/randomizer/option.h b/soh/soh/Enhancements/randomizer/option.h index d2291463b..518487219 100644 --- a/soh/soh/Enhancements/randomizer/option.h +++ b/soh/soh/Enhancements/randomizer/option.h @@ -1,6 +1,7 @@ #pragma once -#include "soh/SohGui/UIWidgets.hpp" +#ifndef RANDOPTION_H +#define RANDOPTION_H #include #include @@ -35,7 +36,6 @@ enum class OptionCategory { */ enum class WidgetType { Checkbox, /** Default for Bools, not compatible if options.size() > 2. */ - TristateCheckbox, /** Compatible with U8s, not compatible if options.size() != 3. */ Combobox, /** Default for U8s, works with U8s and Bools. */ Slider, /** Compatible with U8s. If constructed with NumOpts, consider using this. Technically can be used for Bool or non-NumOpts options but it would be a bit weird semantically. */ }; @@ -312,7 +312,7 @@ class Option { * @param graphic What graphic to display in a disabled checkbox. Defaults to an * "X" symbol. */ - void Disable(std::string text, UIWidgets::CheckboxGraphics graphic = UIWidgets::CheckboxGraphics::Cross); + void Disable(std::string text); bool IsCategory(OptionCategory category) const; /** @@ -339,7 +339,6 @@ protected: private: bool RenderCheckbox(); - bool RenderTristateCheckbox(); bool RenderCombobox(); bool RenderSlider(); void PopulateTextToNum(); @@ -357,7 +356,6 @@ protected: bool defaultHidden = false; int imFlags = IMFLAG_NONE; bool disabled = false; - UIWidgets::CheckboxGraphics disabledGraphic = UIWidgets::CheckboxGraphics::Cross; std::string disabledText; std::unordered_map optionsTextToVar = {}; }; @@ -546,4 +544,6 @@ class OptionGroup { std::string mDescription; bool mDisabled = false; }; -} // namespace Rando \ No newline at end of file +} // namespace Rando + +#endif //RANDOPTION_H \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 44831c7c0..85ddc6c50 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -11,9 +11,8 @@ #include "3drando/rando_main.hpp" #include "3drando/random.hpp" #include "soh/ResourceManagerHelpers.h" -#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "3drando/custom_messages.hpp" -#include "soh/SohGui/UIWidgets.hpp" #include #include #include "../custom-message/CustomMessageTypes.h" @@ -27,6 +26,7 @@ #include #include #include "draw.h" +#include "soh/SohGui/UIWidgets.hpp" #include "static_data.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include @@ -1924,6 +1924,16 @@ bool GenerateRandomizer(std::string seed /*= ""*/) { return false; } +static const std::unordered_map randomizerPresetList = { + { RANDOMIZER_PRESET_DEFAULT, "Default" }, + { RANDOMIZER_PRESET_SPOCK_RACE, "Spock Race" }, + { RANDOMIZER_PRESET_SPOCK_RACE_NO_LOGIC, "Spock Race (No Logic)" }, + { RANDOMIZER_PRESET_S6, "S6" }, + { RANDOMIZER_PRESET_HELL_MODE, "Hell Mode" }, + { RANDOMIZER_PRESET_BENCHMARK, "Benchmark" } +}; +static int32_t randomizerPresetSelected = RANDOMIZER_PRESET_DEFAULT; + void RandomizerSettingsWindow::DrawElement() { auto ctx = Rando::Context::GetInstance(); if (generated) { @@ -1931,30 +1941,62 @@ void RandomizerSettingsWindow::DrawElement() { randoThread.join(); } bool disableEditingRandoSettings = CVarGetInteger(CVAR_GENERAL("RandoGenerating"), 0) || CVarGetInteger(CVAR_GENERAL("OnFileSelectNameEntry"), 0); - if (disableEditingRandoSettings) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0) || disableEditingRandoSettings); + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_RANDOMIZER); + std::string comboboxTooltip = ""; + for (auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter) { + if (iter->first != 0) comboboxTooltip += "\n\n"; + comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); + } + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_RANDOMIZER); + randomizerPresetSelected = CVarGetInteger(presetTypeCvar.c_str(), RANDOMIZER_PRESET_DEFAULT); + + if (UIWidgets::Combobox("Randomizer Presets", &randomizerPresetSelected, randomizerPresetList, UIWidgets::ComboboxOptions() + .DefaultIndex(RANDOMIZER_PRESET_DEFAULT) + .Tooltip(comboboxTooltip.c_str()) + .Color(THEME_COLOR)) + ) { + CVarSetInteger(presetTypeCvar.c_str(), randomizerPresetSelected); + } + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 35.f); + if (UIWidgets::Button("Apply Preset##Randomizer", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { + if (randomizerPresetSelected >= presetTypeDef.presets.size()) { + randomizerPresetSelected = 0; + } + const PresetDefinition selectedPresetDef = presetTypeDef.presets.at(randomizerPresetSelected); + for(const char* block : presetTypeDef.blocksToClear) { + CVarClearBlock(block); + } + if (randomizerPresetSelected != 0) { + applyPreset(selectedPresetDef.entries); + } + CVarSetInteger(presetTypeCvar.c_str(), randomizerPresetSelected); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - DrawPresetSelector(PRESET_TYPE_RANDOMIZER); - ImGui::EndDisabled(); - UIWidgets::Spacer(0); - UIWidgets::EnhancementCheckbox("Manual seed entry", CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), false, ""); + UIWidgets::CVarCheckbox("Manual seed entry", CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0)) { - ImGui::Text("Seed"); + UIWidgets::PushStyleInput(THEME_COLOR); ImGui::InputText("##RandomizerSeed", seedString, MAX_SEED_STRING_SIZE, ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterAlphaNum); UIWidgets::Tooltip( "Characters from a-z, A-Z, and 0-9 are supported.\n" "Character limit is 1023, after which the seed will be truncated.\n" ); - ImGui::SameLine(); - if (ImGui::Button("New Seed")) { + if (strnlen(seedString, MAX_SEED_STRING_SIZE) == 0) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Leave blank for random seed"); + } + UIWidgets::PopStyleInput(); + ImGui::SameLine(0.f, 50.f); + if (UIWidgets::Button(ICON_FA_RANDOM, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)).Tooltip( + "Creates a new random seed value to be used when generating a randomizer" + ))) { SohUtils::CopyStringToCharArray(seedString, std::to_string(rand() & 0xFFFFFFFF), MAX_SEED_STRING_SIZE); } - UIWidgets::Tooltip("Creates a new random seed value to be used when generating a randomizer"); ImGui::SameLine(); - if (ImGui::Button("Clear Seed")) { + if (UIWidgets::Button(ICON_FA_ERASER, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)))) { memset(seedString, 0, MAX_SEED_STRING_SIZE); } } @@ -1962,13 +2004,13 @@ void RandomizerSettingsWindow::DrawElement() { UIWidgets::Spacer(0); ImGui::BeginDisabled((CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0) && gSaveContext.gameMode != GAMEMODE_FILE_SELECT) || GameInteractor::IsSaveLoaded()); - if (ImGui::Button("Generate Randomizer")) { + if (UIWidgets::Button("Generate Randomizer", UIWidgets::ButtonOptions().Size(ImVec2(250.f, 0.f)).Color(THEME_COLOR))) { ctx->SetSpoilerLoaded(false); GenerateRandomizer(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0) ? seedString : ""); } ImGui::EndDisabled(); - UIWidgets::Spacer(0); + ImGui::SameLine(); if (!CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0)) { std::string spoilerfilepath = CVarGetString(CVAR_GENERAL("SpoilerLog"), ""); ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str()); @@ -1978,13 +2020,13 @@ void RandomizerSettingsWindow::DrawElement() { // std::string presetfilepath = CVarGetString(CVAR_RANDOMIZER_SETTING("LoadedPreset"), ""); // ImGui::Text("Settings File: %s", presetfilepath.c_str()); - UIWidgets::PaddedSeparator(); - + UIWidgets::Separator(true, true, 0.f, 0.f); ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); ImGuiWindow* window = ImGui::GetCurrentWindow(); static ImVec2 cellPadding(8.0f, 8.0f); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Randomizer Settings", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("World")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); @@ -2048,7 +2090,9 @@ void RandomizerSettingsWindow::DrawElement() { window->DC.CurrLineTextBaseOffset = 0.0f; static ImGuiTextFilter locationSearch; + UIWidgets::PushStyleInput(THEME_COLOR); locationSearch.Draw(); + UIWidgets::PopStyleInput(); ImGui::BeginChild("ChildIncludedLocations", ImVec2(0, -8)); for (auto& [rcArea, locations] : RandomizerCheckObjects::GetAllRCObjectsByArea()) { @@ -2068,7 +2112,7 @@ void RandomizerSettingsWindow::DrawElement() { for (auto& location : locations) { if (ctx->GetItemLocation(location)->IsVisible() && !excludedLocations.count(location) && locationSearch.PassFilter(Rando::StaticData::GetLocation(location)->GetName().c_str())) { - + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(location).c_str(), ImGuiDir_Right)) { excludedLocations.insert(location); // todo: this efficently when we build out cvar array support @@ -2080,6 +2124,7 @@ void RandomizerSettingsWindow::DrawElement() { CVarSetString(CVAR_RANDOMIZER_SETTING("ExcludedLocations"), excludedLocationString.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s", Rando::StaticData::GetLocation(location)->GetShortName().c_str()); } @@ -2110,6 +2155,7 @@ void RandomizerSettingsWindow::DrawElement() { for (auto& location : locations) { auto elfound = excludedLocations.find(location); if (ctx->GetItemLocation(location)->IsVisible() && elfound != excludedLocations.end()) { + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(location).c_str(), ImGuiDir_Left)) { excludedLocations.erase(elfound); // todo: this efficently when we build out cvar array support @@ -2125,6 +2171,7 @@ void RandomizerSettingsWindow::DrawElement() { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s", Rando::StaticData::GetLocation(location)->GetShortName().c_str()); } @@ -2272,10 +2319,12 @@ void RandomizerSettingsWindow::DrawElement() { //{ Rando::Tricks::Tag::GLITCH, false }, }; static ImGuiTextFilter trickSearch; + UIWidgets::PushStyleInput(THEME_COLOR); trickSearch.Draw("Filter (inc,-exc)", 490.0f); + UIWidgets::PopStyleInput(); if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("LogicRules"), RO_LOGIC_GLITCHLESS) != RO_LOGIC_NO_LOGIC) { ImGui::SameLine(); - if (ImGui::Button("Disable All")) { + if (UIWidgets::Button("Disable All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(250.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto etfound = enabledTricks.find(static_cast(i)); if (etfound != enabledTricks.end()) { @@ -2291,7 +2340,7 @@ void RandomizerSettingsWindow::DrawElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Enable All")) { + if (UIWidgets::Button("Enable All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(250.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { if (!enabledTricks.count(static_cast(i))) { enabledTricks.insert(static_cast(i)); @@ -2334,19 +2383,19 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::TableNextColumn(); window->DC.CurrLineTextBaseOffset = 0.0f; - if (ImGui::Button("Collapse All##disabled")) { + if (UIWidgets::Button("Collapse All##disabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeDisabled[static_cast(i)] = false; } } ImGui::SameLine(); - if (ImGui::Button("Open All##disabled")) { + if (UIWidgets::Button("Open All##disabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeDisabled[static_cast(i)] = true; } } ImGui::SameLine(); - if (ImGui::Button("Enable Visible")) { + if (UIWidgets::Button("Enable Visible", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto option = mSettings->GetTrickOption(static_cast(i)); if (!enabledTricks.count(static_cast(i)) && @@ -2387,6 +2436,7 @@ void RandomizerSettingsWindow::DrawElement() { !enabledTricks.count(rt) && Rando::Tricks::CheckTags(showTag, option.GetTags())) { ImGui::TreeNodeSetOpen(ImGui::GetID((Rando::Tricks::GetAreaName(option.GetArea()) + "##disabled").c_str()), areaTreeDisabled[option.GetArea()]); ImGui::SetNextItemOpen(true, ImGuiCond_Once); + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(rt).c_str(), ImGuiDir_Right)) { enabledTricks.insert(rt); std::string enabledTrickString = ""; @@ -2397,10 +2447,11 @@ void RandomizerSettingsWindow::DrawElement() { CVarSetString(CVAR_RANDOMIZER_SETTING("EnabledTricks"), enabledTrickString.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - Rando::Tricks::DrawTagChips(option.GetTags()); + UIWidgets::PopStyleButton(); + Rando::Tricks::DrawTagChips(option.GetTags(), option.GetName()); ImGui::SameLine(); ImGui::Text("%s", option.GetName().c_str()); - UIWidgets::InsertHelpHoverText(option.GetDescription().c_str()); + UIWidgets::Tooltip(option.GetDescription().c_str()); } } areaTreeDisabled[area] = true; @@ -2416,19 +2467,19 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::TableNextColumn(); window->DC.CurrLineTextBaseOffset = 0.0f; - if (ImGui::Button("Collapse All##enabled")) { + if (UIWidgets::Button("Collapse All##enabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeEnabled[static_cast(i)] = false; } } ImGui::SameLine(); - if (ImGui::Button("Open All##enabled")) { + if (UIWidgets::Button("Open All##enabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeEnabled[static_cast(i)] = true; } } ImGui::SameLine(); - if (ImGui::Button("Disable Visible")) { + if (UIWidgets::Button("Disable Visible", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto option = mSettings->GetTrickOption(static_cast(i)); if (enabledTricks.count(static_cast(i)) && @@ -2473,6 +2524,7 @@ void RandomizerSettingsWindow::DrawElement() { enabledTricks.count(rt) && Rando::Tricks::CheckTags(showTag, option.GetTags())) { ImGui::TreeNodeSetOpen(ImGui::GetID((Rando::Tricks::GetAreaName(option.GetArea()) + "##enabled").c_str()), areaTreeEnabled[option.GetArea()]); ImGui::SetNextItemOpen(true, ImGuiCond_Once); + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(rt).c_str(), ImGuiDir_Left)) { enabledTricks.erase(rt); std::string enabledTrickString = ""; @@ -2487,10 +2539,11 @@ void RandomizerSettingsWindow::DrawElement() { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - Rando::Tricks::DrawTagChips(option.GetTags()); + UIWidgets::PopStyleButton(); + Rando::Tricks::DrawTagChips(option.GetTags(), option.GetName()); ImGui::SameLine(); ImGui::Text("%s", option.GetName().c_str()); - UIWidgets::InsertHelpHoverText(option.GetDescription().c_str()); + UIWidgets::Tooltip(option.GetDescription().c_str()); } } areaTreeEnabled[area] = true; @@ -2532,12 +2585,10 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); ImGui::EndDisabled(); - - if (disableEditingRandoSettings) { - UIWidgets::ReEnableComponent(""); - } + ImGui::EndDisabled(); } void RandomizerSettingsWindow::UpdateElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index e3b07b672..cc139fbfd 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -7,6 +7,7 @@ #include "soh/SaveManager.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "dungeon.h" #include "location_access.h" @@ -844,6 +845,39 @@ void SetAreaSpoiled(RandomizerCheckArea rcArea) { } void CheckTrackerWindow::DrawElement() { + Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor.Value"), Color_Bg_Default); + Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); + Color_Area_Incomplete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor.Value"), Color_Area_Incomplete_Extra_Default); + Color_Area_Complete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor.Value"), Color_Main_Default); + Color_Area_Complete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor.Value"), Color_Area_Complete_Extra_Default); + Color_Unchecked_Main = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.MainColor.Value"), Color_Main_Default); + Color_Unchecked_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.ExtraColor.Value"), Color_Unchecked_Extra_Default); + Color_Skipped_Main = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.MainColor.Value"), Color_Main_Default); + Color_Skipped_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.ExtraColor.Value"), Color_Skipped_Extra_Default); + Color_Seen_Main = CVarGetColor(CVAR_TRACKER_CHECK("Seen.MainColor.Value"), Color_Main_Default); + Color_Seen_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Seen.ExtraColor.Value"), Color_Seen_Extra_Default); + Color_Hinted_Main = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.MainColor.Value"), Color_Main_Default); + Color_Hinted_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.ExtraColor.Value"), Color_Hinted_Extra_Default); + Color_Collected_Main = CVarGetColor(CVAR_TRACKER_CHECK("Collected.MainColor.Value"), Color_Main_Default); + Color_Collected_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Collected.ExtraColor.Value"), Color_Collected_Extra_Default); + Color_Scummed_Main = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.MainColor.Value"), Color_Main_Default); + Color_Scummed_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.ExtraColor.Value"), Color_Scummed_Extra_Default); + Color_Saved_Main = CVarGetColor(CVAR_TRACKER_CHECK("Saved.MainColor.Value"), Color_Main_Default); + Color_Saved_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Saved.ExtraColor.Value"), Color_Saved_Extra_Default); + hideUnchecked = CVarGetInteger(CVAR_TRACKER_CHECK("Unchecked.Hide"), 0); + hideScummed = CVarGetInteger(CVAR_TRACKER_CHECK("Scummed.Hide"), 0); + hideSeen = CVarGetInteger(CVAR_TRACKER_CHECK("Seen.Hide"), 0); + hideSkipped = CVarGetInteger(CVAR_TRACKER_CHECK("Skipped.Hide"), 0); + hideSaved = CVarGetInteger(CVAR_TRACKER_CHECK("Saved.Hide"), 0); + hideCollected = CVarGetInteger(CVAR_TRACKER_CHECK("Collected.Hide"), 0); + showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); + mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0); + showLogicTooltip = CVarGetInteger(CVAR_TRACKER_CHECK("ShowLogic"), 0); + + hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 1); + alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); + + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { if (CVarGetInteger(CVAR_TRACKER_CHECK("ShowOnlyPaused"), 0) && (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) { return; @@ -863,11 +897,11 @@ void CheckTrackerWindow::DrawElement() { } ImGui::SetNextWindowSize(ImVec2(400, 540), ImGuiCond_FirstUseEver); - BeginFloatWindows("Check Tracker", mIsVisible, ImGuiWindowFlags_NoScrollbar); if (!GameInteractor::IsSaveLoaded() || !initialized) { ImGui::Text("Waiting for file load..."); //TODO Language + ImGui::PopFont(); EndFloatWindows(); return; } @@ -886,38 +920,40 @@ void CheckTrackerWindow::DrawElement() { ImVec2 size = ImGui::GetContentRegionMax(); size.y -= headerHeight; if (!ImGui::BeginTable("Check Tracker", 1, 0, size)) { + ImGui::PopFont(); EndFloatWindows(); return; } ImGui::TableNextRow(0, headerHeight); ImGui::TableNextColumn(); - UIWidgets::EnhancementCheckbox( - "Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), false, - "When active, items will show hidden checks by default when updated to this state."); + UIWidgets::CVarCheckbox( + "Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), UIWidgets::CheckboxOptions({{ .tooltip = "When active, items will show hidden checks by default when updated to this state." }}) + .Color(THEME_COLOR)); UIWidgets::PaddedSeparator(); - if (ImGui::Button("Expand All")) { + if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { optCollapseAll = false; optExpandAll = true; doAreaScroll = true; } ImGui::SameLine(); - if (ImGui::Button("Collapse All")) { + if (UIWidgets::Button("Collapse All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { optExpandAll = false; optCollapseAll = true; } ImGui::SameLine(); - if (ImGui::Button("Clear")) { + if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({{ .tooltip = "Clear the search field" }}).Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { checkSearch.Clear(); UpdateFilters(); doAreaScroll = true; } - UIWidgets::Tooltip("Clear the search field"); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (checkSearch.Draw()) { UpdateFilters(); } + UIWidgets::PopStyleCombobox(); - UIWidgets::PaddedSeparator(); + ImGui::Separator(); ImGui::Text("Total Checks: %d / %d", totalChecksGotten, totalChecks); @@ -929,6 +965,7 @@ void CheckTrackerWindow::DrawElement() { size = ImGui::GetContentRegionAvail(); if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY, size)) { ImGui::EndTable(); + ImGui::PopFont(); EndFloatWindows(); return; } @@ -946,16 +983,17 @@ void CheckTrackerWindow::DrawElement() { bool doingCollapseOrExpand = optExpandAll || optCollapseAll; bool isThisAreaSpoiled; RandomizerCheckArea lastArea = RCAREA_INVALID; - Color_RGBA8 areaCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor"), Color_Main_Default); - Color_RGBA8 areaIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), Color_Main_Default); - Color_RGBA8 extraCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Extra_Default); - Color_RGBA8 extraIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Extra_Default); + Color_RGBA8 areaCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor.Value"), Color_Main_Default); + Color_RGBA8 areaIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); + Color_RGBA8 extraCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor.Value"), Color_Area_Complete_Extra_Default); + Color_RGBA8 extraIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor.Value"), Color_Area_Incomplete_Extra_Default); Color_RGBA8 mainColor; Color_RGBA8 extraColor; std::string stemp; bool shouldHideFilteredAreas = CVarGetInteger(CVAR_TRACKER_CHECK("HideFilteredAreas"), 1); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); for (auto& [rcArea, checks] : checksByArea) { RandomizerCheckArea thisArea = currentArea; @@ -1039,9 +1077,11 @@ void CheckTrackerWindow::DrawElement() { } } } + ImGui::PopStyleVar(); ImGui::EndTable(); //Checks Lead-out ImGui::EndTable(); //Quick Options Lead-out + ImGui::PopFont(); EndFloatWindows(); if (doingCollapseOrExpand) { optCollapseAll = false; @@ -1099,7 +1139,7 @@ void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flag windowFlags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar; - if (!CVarGetInteger(CVAR_TRACKER_CHECK("Draggable"), 0)) { + if (!CVarGetInteger(CVAR_TRACKER_CHECK("Draggable"), 1)) { windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; } } @@ -1468,54 +1508,54 @@ void DrawLocation(RandomizerCheck rc) { } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Collected_Extra_Default + ? Color_Collected_Extra : Color_Collected_Main; - extraColor = Color_Collected_Extra_Default; + extraColor = Color_Collected_Extra; } else if (status == RCSHOW_SAVED) { if (!showHidden && hideSaved) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Saved_Extra_Default + ? Color_Saved_Extra : Color_Saved_Main; - extraColor = Color_Saved_Extra_Default; + extraColor = Color_Saved_Extra; } else if (skipped) { if (!showHidden && hideSkipped) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Skipped_Extra_Default + ? Color_Skipped_Extra : Color_Skipped_Main; - extraColor = Color_Skipped_Extra_Default; + extraColor = Color_Skipped_Extra; } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { if (!showHidden && hideSeen) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Seen_Extra_Default + ? Color_Seen_Extra : Color_Seen_Main; - extraColor = Color_Seen_Extra_Default; + extraColor = Color_Seen_Extra; } else if (status == RCSHOW_SCUMMED) { if (!showHidden && hideScummed) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Scummed_Extra_Default + ? Color_Scummed_Extra : Color_Scummed_Main; - extraColor = Color_Scummed_Extra_Default; + extraColor = Color_Scummed_Extra; } else if (status == RCSHOW_UNCHECKED) { if (!showHidden && hideUnchecked) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Unchecked_Extra_Default + ? Color_Unchecked_Extra : Color_Unchecked_Main; - extraColor = Color_Unchecked_Extra_Default; + extraColor = Color_Unchecked_Extra; } //Main Text @@ -1530,8 +1570,10 @@ void DrawLocation(RandomizerCheck rc) { } // Draw button - for Skipped/Seen/Scummed/Unchecked only + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {4.0f, 3.0f}); + float sz = ImGui::GetFrameHeight(); if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || status == RCSHOW_SCUMMED || skipped) { - if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES)) { + if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES, ImVec2(sz, sz), UIWidgets::ButtonOptions().Color(THEME_COLOR))) { if (skipped) { OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false); areaChecksGotten[loc->GetArea()]--; @@ -1546,8 +1588,10 @@ void DrawLocation(RandomizerCheck rc) { SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); } } else { - ImGui::Dummy(ImVec2(20.0f, 10.0f)); + ImGui::Dummy(ImVec2(sz, sz)); } + ImGui::PopStyleVar(); + ImGui::SameLine(); //Draw @@ -1621,7 +1665,7 @@ void DrawLocation(RandomizerCheck rc) { if (locationInRegion.GetLocation() == rc) { std::string conditionStr = locationInRegion.GetConditionStr(); if (conditionStr != "true") { - UIWidgets::InsertHelpHoverText(conditionStr); + UIWidgets::Tooltip(conditionStr.c_str()); } return; } @@ -1645,7 +1689,7 @@ int hue = 0; void RainbowTick() { float freqHue = hue * 2 * M_PI / (360 * CVarGetFloat(CVAR_COSMETIC("RainbowSpeed"), 0.6f)); for (auto& cvar : rainbowCVars) { - if (CVarGetInteger((cvar + "RBM").c_str(), 0) == 0) { + if (CVarGetInteger((cvar + ".Rainbow").c_str(), 0) == 0) { continue; } @@ -1655,7 +1699,7 @@ void RainbowTick() { newColor.b = sin(freqHue + (4 * M_PI / 3)) * 127 + 128; newColor.a = 255; - CVarSetColor(cvar.c_str(), newColor); + CVarSetColor((cvar + ".Value").c_str(), newColor); } hue++; @@ -1664,46 +1708,33 @@ void RainbowTick() { void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, const char* cvarExtraName, Color_RGBA8& main_color, Color_RGBA8& extra_color, Color_RGBA8& main_default_color, - Color_RGBA8& extra_default_color, const char* cvarHideName, const char* tooltip) { + Color_RGBA8& extra_default_color, const char* cvarHideName, const char* tooltip, UIWidgets::Colors theme) { Color_RGBA8 cvarMainColor = CVarGetColor(cvarMainName, main_default_color); Color_RGBA8 cvarExtraColor = CVarGetColor(cvarExtraName, extra_default_color); main_color = cvarMainColor; extra_color = cvarExtraColor; + UIWidgets::PushStyleCombobox(theme); if (ImGui::CollapsingHeader(text)) { if (*cvarHideName != '\0') { std::string label = cvarHideName; label += "##Hidden"; ImGui::PushID(label.c_str()); - UIWidgets::EnhancementCheckbox("Hidden", cvarHideName, false, - "When active, checks will hide by default when updated to this state. Can " - "be overriden with the \"Show Hidden Items\" option."); + UIWidgets::CVarCheckbox("Hidden", cvarHideName, + UIWidgets::CheckboxOptions({{ .tooltip = "When active, checks will hide by default when updated to this state. Can " + "be overriden with the \"Show Hidden Items\" option." }}).Color(theme)); ImGui::PopID(); } - if (ImGui::BeginTable(text, 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV | ImGuiTableFlags_Hideable)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("Check", cvarMainName, - ImVec4(main_color.r, main_color.g, main_color.b, main_color.a), - ImVec4(main_default_color.r, main_default_color.g, main_default_color.b, main_default_color.a))) - { - main_color = CVarGetColor(cvarMainName, main_default_color); - }; - ImGui::PopItemWidth(); + std::string mainLabel = "Name##" + std::string(cvarMainName); + if (UIWidgets::CVarColorPicker(mainLabel.c_str(), cvarMainName, main_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRainbowCheck, theme)) { + main_color = CVarGetColor(cvarMainName, main_default_color); + } - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("Details", cvarExtraName, - ImVec4(extra_color.r, extra_color.g, extra_color.b, extra_color.a), - ImVec4(extra_default_color.r, extra_default_color.g, extra_default_color.b, extra_default_color.a))) - { - extra_color = CVarGetColor(cvarExtraName, extra_default_color); - } - ImGui::PopItemWidth(); - - ImGui::EndTable(); + std::string extraLabel = "Details##" + std::string(cvarExtraName); + if (UIWidgets::CVarColorPicker(extraLabel.c_str(), cvarExtraName, extra_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRainbowCheck, theme)) { + extra_color = CVarGetColor(cvarExtraName, extra_default_color); } } if (tooltip != NULL && strlen(tooltip) != 0) { @@ -1711,6 +1742,7 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, ImGui::Text(" ?"); UIWidgets::Tooltip(tooltip); } + UIWidgets::PopStyleCombobox(); } void CheckTrackerWindow::Draw() { @@ -1722,73 +1754,84 @@ void CheckTrackerWindow::Draw() { SyncVisibilityConsoleVariable(); } -static const char* windowType[] = { "Floating", "Window" }; -static const char* displayType[] = { "Always", "Combo Button Hold" }; -static const char* buttonStrings[] = { "A Button", "B Button", "C-Up", "C-Down", "C-Left", "C-Right", "L Button", - "Z Button", "R Button", "Start", "D-Up", "D-Down", "D-Left", "D-Right" }; +static std::unordered_map windowType = {{ TRACKER_WINDOW_FLOATING, "Floating" }, { TRACKER_WINDOW_WINDOW, "Window" }}; +static std::unordered_map displayType = {{ 0, "Always" }, { 1, "Combo Button Hold" }}; +static std::unordered_map buttonStrings = { + { TRACKER_COMBO_BUTTON_A, "A Button" }, { TRACKER_COMBO_BUTTON_B, "B Button" }, { TRACKER_COMBO_BUTTON_C_UP, "C-Up" }, + { TRACKER_COMBO_BUTTON_C_DOWN, "C-Down" }, { TRACKER_COMBO_BUTTON_C_LEFT, "C-Left" }, { TRACKER_COMBO_BUTTON_C_RIGHT, "C-Right" }, + { TRACKER_COMBO_BUTTON_L, "L Button" }, { TRACKER_COMBO_BUTTON_Z, "Z Button" }, { TRACKER_COMBO_BUTTON_R, "R Button" }, + { TRACKER_COMBO_BUTTON_START, "Start" }, { TRACKER_COMBO_BUTTON_D_UP, "D-Up" }, { TRACKER_COMBO_BUTTON_D_DOWN, "D-Down" }, + { TRACKER_COMBO_BUTTON_D_LEFT, "D-Left" }, { TRACKER_COMBO_BUTTON_D_RIGHT, "D-Right" }}; void CheckTrackerSettingsWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); - ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); - ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Section settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("BG Color", CVAR_TRACKER_CHECK("BgColor"), - ImVec4(Color_Background.r, Color_Background.g, Color_Background.b, Color_Background.a), - ImVec4(Color_Bg_Default.r, Color_Bg_Default.g, Color_Bg_Default.b, Color_Bg_Default.a), - false, true)) - { - Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default); - } - ImGui::PopItemWidth(); + if (ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn("Section settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + UIWidgets::CVarColorPicker("BG Color", CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default, true, + UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRandomButton, THEME_COLOR); + ImGui::PopItemWidth(); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Window Type", CVAR_TRACKER_CHECK("WindowType"), windowType, TRACKER_WINDOW_WINDOW); - if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { - UIWidgets::EnhancementCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable")); - UIWidgets::EnhancementCheckbox("Only enable while paused", CVAR_TRACKER_CHECK("ShowOnlyPaused")); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), displayType, 0); - if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { - UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, TRACKER_COMBO_BUTTON_L); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, TRACKER_COMBO_BUTTON_R); + UIWidgets::CVarCombobox("Window Type", CVAR_TRACKER_CHECK("WindowType"), windowType, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_WINDOW_WINDOW)); + + if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + UIWidgets::CVarCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Only enable while paused", CVAR_TRACKER_CHECK("ShowOnlyPaused"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), displayType, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(0)); + if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { + UIWidgets::CVarCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); + UIWidgets::CVarCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); + } } + UIWidgets::CVarCheckbox("Vanilla/MQ Dungeon Spoilers", CVAR_TRACKER_CHECK("MQSpoilers"), UIWidgets::CheckboxOptions() + .Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked.").Color(THEME_COLOR)); + if (UIWidgets::CVarCheckbox("Hide unshuffled shop item checks", CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles.").Color(THEME_COLOR))) { + hideShopUnshuffledChecks = !hideShopUnshuffledChecks; + UpdateFilters(); + } + if (UIWidgets::CVarCheckbox("Always show gold skulltulas", CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings.").Color(THEME_COLOR))) { + alwaysShowGS = !alwaysShowGS; + UpdateFilters(); + } + UIWidgets::CVarCheckbox("Show Logic", CVAR_TRACKER_CHECK("ShowLogic"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will show a check's logic when hovering over it.").Color(THEME_COLOR)); + + // Filtering settings + UIWidgets::PaddedSeparator(); + UIWidgets::CVarCheckbox("Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will hide area headers that have no locations matching filter").Color(THEME_COLOR).DefaultValue(true)); + + ImGui::TableNextColumn(); + + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", CVAR_TRACKER_CHECK("AreaComplete.MainColor"), CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, CVAR_TRACKER_CHECK("AreaComplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Unchecked", CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, CVAR_TRACKER_CHECK("Unchecked.Hide"), "Checks you have not interacted with at all.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Skipped", CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, CVAR_TRACKER_CHECK("Skipped.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Seen", CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, CVAR_TRACKER_CHECK("Seen.Hide"), "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy mode.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Scummed", CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, CVAR_TRACKER_CHECK("Scummed.Hide"), "Checks you collect, but then reload before saving so you no longer have them.", THEME_COLOR); + //CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", CVAR_TRACKER_CHECK("Hinted.MainColor"), CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, CVAR_TRACKER_CHECK("Hinted.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Collected", CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, CVAR_TRACKER_CHECK("Collected.Hide"), "Checks you have collected without saving or reloading yet.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Saved", CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, CVAR_TRACKER_CHECK("Saved.Hide"), "Checks that you saved the game while having collected.", THEME_COLOR); + + ImGui::PopStyleVar(1); } - UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", CVAR_TRACKER_CHECK("MQSpoilers")); - UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked."); - if (UIWidgets::EnhancementCheckbox("Hide unshuffled shop item checks", CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), false, "", UIWidgets::CheckboxGraphics::Cross, false)) { - hideShopUnshuffledChecks = !hideShopUnshuffledChecks; - UpdateFilters(); - } - UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles."); - if (UIWidgets::EnhancementCheckbox("Always show gold skulltulas", CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), false, "")) { - alwaysShowGS = !alwaysShowGS; - UpdateFilters(); - } - UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings."); - UIWidgets::EnhancementCheckbox("Show Logic", "gCheckTrackerOptionShowLogic"); - UIWidgets::Tooltip("If enabled, will show a check's logic when hovering over it."); - - // Filtering settings - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox("Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), false, "", UIWidgets::CheckboxGraphics::Checkmark, true); - UIWidgets::Tooltip("If enabled, will hide area headers that have no locations matching filter"); - - ImGui::TableNextColumn(); - - CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", CVAR_TRACKER_CHECK("AreaComplete.MainColor"), CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, CVAR_TRACKER_CHECK("AreaComplete.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Unchecked", CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, CVAR_TRACKER_CHECK("Unchecked.Hide"), "Checks you have not interacted with at all."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Skipped", CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, CVAR_TRACKER_CHECK("Skipped.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Seen", CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, CVAR_TRACKER_CHECK("Seen.Hide"), "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy mode."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Scummed", CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, CVAR_TRACKER_CHECK("Scummed.Hide"), "Checks you collect, but then reload before saving so you no longer have them."); - //CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", CVAR_TRACKER_CHECK("Hinted.MainColor"), CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, CVAR_TRACKER_CHECK("Hinted.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Collected", CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, CVAR_TRACKER_CHECK("Collected.Hide"), "Checks you have collected without saving or reloading yet."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Saved", CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, CVAR_TRACKER_CHECK("Saved.Hide"), "Checks that you saved the game while having collected."); - - ImGui::PopStyleVar(1); ImGui::EndTable(); + ImGui::PopFont(); } void CheckTrackerWindow::InitElement() { @@ -1807,36 +1850,5 @@ void CheckTrackerWindow::InitElement() { } void CheckTrackerWindow::UpdateElement() { - Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default); - Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), Color_Main_Default); - Color_Area_Incomplete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Extra_Default); - Color_Area_Complete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor"), Color_Main_Default); - Color_Area_Complete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Extra_Default); - Color_Unchecked_Main = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.MainColor"), Color_Main_Default); - Color_Unchecked_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Extra_Default); - Color_Skipped_Main = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.MainColor"), Color_Main_Default); - Color_Skipped_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Extra_Default); - Color_Seen_Main = CVarGetColor(CVAR_TRACKER_CHECK("Seen.MainColor"), Color_Main_Default); - Color_Seen_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Extra_Default); - Color_Hinted_Main = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.MainColor"), Color_Main_Default); - Color_Hinted_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Extra_Default); - Color_Collected_Main = CVarGetColor(CVAR_TRACKER_CHECK("Collected.MainColor"), Color_Main_Default); - Color_Collected_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Extra_Default); - Color_Scummed_Main = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.MainColor"), Color_Main_Default); - Color_Scummed_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Extra_Default); - Color_Saved_Main = CVarGetColor(CVAR_TRACKER_CHECK("Saved.MainColor"), Color_Main_Default); - Color_Saved_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Extra_Default); - hideUnchecked = CVarGetInteger(CVAR_TRACKER_CHECK("Unchecked.Hide"), 0); - hideScummed = CVarGetInteger(CVAR_TRACKER_CHECK("Scummed.Hide"), 0); - hideSeen = CVarGetInteger(CVAR_TRACKER_CHECK("Seen.Hide"), 0); - hideSkipped = CVarGetInteger(CVAR_TRACKER_CHECK("Skipped.Hide"), 0); - hideSaved = CVarGetInteger(CVAR_TRACKER_CHECK("Saved.Hide"), 0); - hideCollected = CVarGetInteger(CVAR_TRACKER_CHECK("Collected.Hide"), 0); - showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); - mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0); - showLogicTooltip = CVarGetInteger("gCheckTrackerOptionShowLogic", 0); - - hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 1); - alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); } } // namespace CheckTracker diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h index 851910489..c5bcaa074 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h @@ -2,6 +2,7 @@ #include #include "randomizerTypes.h" #include "randomizer_check_objects.h" +#include "soh/SohGui/UIWidgets.hpp" #include diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 4c3cb971e..7a151a983 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -1,7 +1,7 @@ #include "randomizer_entrance_tracker.h" #include "soh/OTRGlobals.h" #include "soh/cvar_prefixes.h" -#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -659,77 +659,70 @@ void InitEntranceTrackingData() { } void EntranceTrackerSettingsWindow::DrawElement() { - if (ImGui::BeginTable("entranceTrackerSettings", 1, ImGuiTableFlags_BordersInnerH)) { + + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); + ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); + UIWidgets::Spacer(0); + + ImGui::TableNextColumn(); + + if (ImGui::BeginTable("entranceTrackerSubSettings", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("column 1", ImGuiTableColumnFlags_WidthStretch, 150.0f); + ImGui::TableSetupColumn("column 2", ImGuiTableColumnFlags_WidthStretch, 150.0f); ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); - UIWidgets::Spacer(0); + ImGui::Text("Sort By"); + UIWidgets::CVarRadioButton("To", CVAR_TRACKER_ENTRANCE("SortBy"), 0, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("Sort entrances by the original source entrance")); + UIWidgets::CVarRadioButton("From", CVAR_TRACKER_ENTRANCE("SortBy"), 1, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR).Tooltip("Sort entrances by the overrided destination")); + + ImGui::Text("List Items"); + UIWidgets::CVarCheckbox("Auto scroll", CVAR_TRACKER_ENTRANCE("AutoScroll"), + UIWidgets::CheckboxOptions().Tooltip("Automatically scroll to the first aviable entrance in the current scene").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Highlight previous", CVAR_TRACKER_ENTRANCE("HighlightPrevious"), + UIWidgets::CheckboxOptions().Tooltip("Highlight the previous entrance that Link came from").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Highlight available", CVAR_TRACKER_ENTRANCE("HighlightAvailable"), + UIWidgets::CheckboxOptions().Tooltip("Highlight available entrances in the current scene").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Hide undiscovered", CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), + UIWidgets::CheckboxOptions().Tooltip("Collapse undiscovered entrances towards the bottom of each group").Color(THEME_COLOR)); + bool disableHideReverseEntrances = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + static const char* disableHideReverseEntrancesText = "This option is disabled because \"Decouple Entrances\" is enabled."; + UIWidgets::CVarCheckbox("Hide reverse", CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), + UIWidgets::CheckboxOptions({ {.disabled = disableHideReverseEntrances, .disabledTooltip = disableHideReverseEntrancesText }}) + .Tooltip("Hide reverse entrance transitions when Decouple Entrances is off").DefaultValue(true).Color(THEME_COLOR)); ImGui::TableNextColumn(); - if (ImGui::BeginTable("entranceTrackerSubSettings", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { + ImGui::Text("Group By"); + UIWidgets::CVarRadioButton( + "Area", CVAR_TRACKER_ENTRANCE("GroupBy"), 0, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("Group entrances by their area")); + UIWidgets::CVarRadioButton( + "Type", CVAR_TRACKER_ENTRANCE("GroupBy"), 1, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("Group entrances by their entrance type")); - ImGui::TableNextColumn(); - - ImGui::Text("Sort By"); - UIWidgets::EnhancementRadioButton("To", CVAR_TRACKER_ENTRANCE("SortBy"), 0); - UIWidgets::Tooltip("Sort entrances by the original source entrance"); - UIWidgets::EnhancementRadioButton("From", CVAR_TRACKER_ENTRANCE("SortBy"), 1); - UIWidgets::Tooltip("Sort entrances by the overrided destination"); - - UIWidgets::Spacer(2.0f); - - ImGui::Text("List Items"); - UIWidgets::PaddedEnhancementCheckbox("Auto scroll", CVAR_TRACKER_ENTRANCE("AutoScroll"), true, false); - UIWidgets::Tooltip("Automatically scroll to the first aviable entrance in the current scene"); - UIWidgets::PaddedEnhancementCheckbox("Highlight previous", CVAR_TRACKER_ENTRANCE("HighlightPrevious"), true, false); - UIWidgets::Tooltip("Highlight the previous entrance that Link came from"); - UIWidgets::PaddedEnhancementCheckbox("Highlight available", CVAR_TRACKER_ENTRANCE("HighlightAvailable"), true, false); - UIWidgets::Tooltip("Highlight available entrances in the current scene"); - UIWidgets::PaddedEnhancementCheckbox("Hide undiscovered", CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), true, false); - UIWidgets::Tooltip("Collapse undiscovered entrances towards the bottom of each group"); - bool disableHideReverseEntrances = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; - static const char* disableHideReverseEntrancesText = "This option is disabled because \"Decouple Entrances\" is enabled."; - UIWidgets::PaddedEnhancementCheckbox("Hide reverse", CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), true, false, - disableHideReverseEntrances, disableHideReverseEntrancesText, UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Hide reverse entrance transitions when Decouple Entrances is off"); - UIWidgets::Spacer(0); - - ImGui::TableNextColumn(); - - ImGui::Text("Group By"); - UIWidgets::EnhancementRadioButton("Area", CVAR_TRACKER_ENTRANCE("GroupBy"), 0); - UIWidgets::Tooltip("Group entrances by their area"); - UIWidgets::EnhancementRadioButton("Type", CVAR_TRACKER_ENTRANCE("GroupBy"), 1); - UIWidgets::Tooltip("Group entrances by their entrance type"); - - UIWidgets::Spacer(2.0f); - - ImGui::Text("Spoiler Reveal"); - UIWidgets::PaddedEnhancementCheckbox("Show Source", CVAR_TRACKER_ENTRANCE("ShowFrom"), true, false); - UIWidgets::Tooltip("Reveal the sourcefor undiscovered entrances"); - UIWidgets::PaddedEnhancementCheckbox("Show Destination", CVAR_TRACKER_ENTRANCE("ShowTo"), true, false); - UIWidgets::Tooltip("Reveal the destination for undiscovered entrances"); - - ImGui::EndTable(); - } - - ImGui::TableNextColumn(); - - ImGui::SetNextItemOpen(false, ImGuiCond_Once); - if (ImGui::TreeNode("Legend")) { - ImGui::TextColored(ImColor(COLOR_ORANGE), "Last Entrance"); - ImGui::TextColored(ImColor(COLOR_GREEN), "Available Entrances"); - ImGui::TextColored(ImColor(COLOR_GRAY), "Undiscovered Entrances"); - ImGui::TreePop(); - } - - UIWidgets::Spacer(0); + ImGui::Text("Spoiler Reveal"); + UIWidgets::CVarCheckbox("Show Source", CVAR_TRACKER_ENTRANCE("ShowFrom"), + UIWidgets::CheckboxOptions().Tooltip("Reveal the sourcefor undiscovered entrances").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show Destination", CVAR_TRACKER_ENTRANCE("ShowTo"), + UIWidgets::CheckboxOptions().Tooltip("Reveal the destination for undiscovered entrances").Color(THEME_COLOR)); ImGui::EndTable(); } + + ImGui::SetNextItemOpen(false, ImGuiCond_Once); + if (ImGui::TreeNode("Legend")) { + ImGui::TextColored(ImColor(COLOR_ORANGE), "Last Entrance"); + ImGui::TextColored(ImColor(COLOR_GREEN), "Available Entrances"); + ImGui::TextColored(ImColor(COLOR_GRAY), "Undiscovered Entrances"); + ImGui::TreePop(); + } + ImGui::PopFont(); } void EntranceTrackerWindow::Draw() { @@ -742,6 +735,7 @@ void EntranceTrackerWindow::Draw() { } void EntranceTrackerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::SetNextWindowSize(ImVec2(600, 375), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Entrance Tracker", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { @@ -752,24 +746,26 @@ void EntranceTrackerWindow::DrawElement() { static ImGuiTextFilter locationSearch; uint8_t nextTreeState = 0; - if (ImGui::Button("Collapse All")) { + if (UIWidgets::Button("Collapse All", UIWidgets::ButtonOptions({{ .tooltip = "Collapse all entrance groups" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { nextTreeState = 1; } - UIWidgets::Tooltip("Collapse all entrance groups"); ImGui::SameLine(); - if (ImGui::Button("Expand All")) { + if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions({{ .tooltip = "Expand all entrance groups" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { nextTreeState = 2; } - UIWidgets::Tooltip("Expand all entrance groups"); ImGui::SameLine(); - if (ImGui::Button("Clear")) { + if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({{ .tooltip = "Clear the search field" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { locationSearch.Clear(); } - UIWidgets::Tooltip("Clear the search field"); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (locationSearch.Draw()) { nextTreeState = 2; } + UIWidgets::PopStyleCombobox(); uint8_t destToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("SortBy"), 0); uint8_t groupToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("GroupBy"), 0); @@ -940,8 +936,8 @@ void EntranceTrackerWindow::DrawElement() { } } ImGui::EndChild(); - ImGui::End(); + ImGui::PopFont(); } void EntranceTrackerWindow::InitElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h index ef217b7f0..0e1271e57 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h @@ -100,8 +100,8 @@ class EntranceTrackerSettingsWindow : public Ship::GuiWindow { class EntranceTrackerWindow : public Ship::GuiWindow { public: using GuiWindow::GuiWindow; - void Draw() override; + void InitElement() override; void DrawElement() override; void UpdateElement() override {}; diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index b2343b000..828fcda2d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -5,6 +5,7 @@ #include "soh/SaveManager.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "randomizerTypes.h" #include @@ -35,6 +36,8 @@ void DrawSong(ItemTrackerItem item); int itemTrackerSectionId; +using namespace UIWidgets; + bool shouldUpdateVectors = true; std::vector mainWindowItems = {}; @@ -616,7 +619,7 @@ void DrawEquip(ItemTrackerItem item) { ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasEquip && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); } void DrawQuest(ItemTrackerItem item) { @@ -632,7 +635,7 @@ void DrawQuest(ItemTrackerItem item) { ImGui::EndGroup(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(item.id)); + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); }; void DrawItem(ItemTrackerItem item) { @@ -800,7 +803,7 @@ void DrawItem(ItemTrackerItem item) { itemName = SohUtils::GetItemName(item.id); } - UIWidgets::SetLastItemHoverText(itemName); + Tooltip(itemName.c_str()); } void DrawBottle(ItemTrackerItem item) { @@ -815,7 +818,7 @@ void DrawBottle(ItemTrackerItem item) { ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasItem && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); }; void DrawDungeonItem(ItemTrackerItem item) { @@ -864,7 +867,7 @@ void DrawDungeonItem(ItemTrackerItem item) { } ImGui::EndGroup(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); } void DrawSong(ItemTrackerItem item) { @@ -874,7 +877,7 @@ void DrawSong(ItemTrackerItem item) { ImGui::SetCursorScreenPos(ImVec2(p.x + 6, p.y)); ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasSong && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize / 1.5, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(item.id)); + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); } void DrawNotes(bool resizeable = false) { @@ -931,7 +934,6 @@ void DrawTotalChecks() { } // Windowing stuff -ImVec4 ChromaKeyBackground = { 0, 0, 0, 0 }; // Float value, 1 = 255 in rgb value. void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { ImGuiWindowFlags windowFlags = flags; @@ -947,7 +949,7 @@ void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; } } - ImGui::PushStyleColor(ImGuiCol_WindowBg, ChromaKeyBackground); + ImGui::PushStyleColor(ImGuiCol_WindowBg, VecFromRGBA8(CVarGetColor(CVAR_TRACKER_ITEM("BgColor.Value"), {0, 0, 0, 0}))); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); ImGui::Begin(UniqueName.c_str(), nullptr, windowFlags); @@ -1220,6 +1222,7 @@ void ItemTrackerWindow::Draw() { } void ItemTrackerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); UpdateVectors(); int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); @@ -1350,19 +1353,29 @@ void ItemTrackerWindow::DrawElement() { EndFloatingWindows(); } } + ImGui::PopFont(); } -static const char* itemTrackerCapacityTrackOptions[5] = { "No Numbers", "Current Capacity", "Current Ammo", "Current Capacity / Max Capacity", "Current Ammo / Current Capacity" }; -static const char* itemTrackerKeyTrackOptions[3] = { "Collected / Max", "Current / Collected / Max", "Current / Max" }; -static const char* itemTrackerTriforcePieceTrackOptions[2] = { "Collected / Required", "Collected / Required / Max" }; -static const char* windowTypes[2] = { "Floating", "Window" }; -static const char* displayModes[2] = { "Always", "Combo Button Hold" }; -static const char* buttons[14] = { "A", "B", "C-Up", "C-Down", "C-Left", "C-Right", "L", "Z", "R", "Start", "D-Up", "D-Down", "D-Left", "D-Right" }; -static const char* displayTypes[3] = { "Hidden", "Main Window", "Separate" }; -static const char* extendedDisplayTypes[4] = { "Hidden", "Main Window", "Misc Window", "Separate" }; -static const char* minimalDisplayTypes[2] = { "Hidden", "Separate" }; +static std::unordered_map itemTrackerCapacityTrackOptions = { + { ITEM_TRACKER_NUMBER_NONE, "No Numbers" }, { ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY, "Current Capacity" }, { ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY, "Current Ammo" }, + { ITEM_TRACKER_NUMBER_CAPACITY, "Current Capacity / Max Capacity" }, { ITEM_TRACKER_NUMBER_AMMO, "Current Ammo / Current Capacity" }}; +static std::unordered_map itemTrackerKeyTrackOptions = { + { KEYS_COLLECTED_MAX, "Collected / Max" }, { KEYS_CURRENT_COLLECTED_MAX, "Current / Collected / Max" }, { KEYS_CURRENT_MAX, "Current / Max" }}; +static std::unordered_map itemTrackerTriforcePieceTrackOptions = { + { TRIFORCE_PIECE_COLLECTED_REQUIRED, "Collected / Required" }, { TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX, "Collected / Required / Max" }}; +static std::unordered_map windowTypes = {{ TRACKER_WINDOW_FLOATING, "Floating" }, { TRACKER_WINDOW_WINDOW, "Window" }}; +static std::unordered_map displayModes = {{ TRACKER_DISPLAY_ALWAYS, "Always" }, { TRACKER_DISPLAY_COMBO_BUTTON, "Combo Button Hold" }}; +static std::unordered_map buttons = {{ TRACKER_COMBO_BUTTON_A, "A" }, { TRACKER_COMBO_BUTTON_B, "B"}, { TRACKER_COMBO_BUTTON_C_UP, "C-Up"}, + { TRACKER_COMBO_BUTTON_C_DOWN, "C-Down" }, { TRACKER_COMBO_BUTTON_C_LEFT, "C-Left" }, { TRACKER_COMBO_BUTTON_C_RIGHT, "C-Right" }, { TRACKER_COMBO_BUTTON_L, "L" }, + { TRACKER_COMBO_BUTTON_Z, "Z" }, { TRACKER_COMBO_BUTTON_R, "R" }, { TRACKER_COMBO_BUTTON_START, "Start" }, { TRACKER_COMBO_BUTTON_D_UP, "D-Up" }, + { TRACKER_COMBO_BUTTON_D_DOWN, "D-Down" }, { TRACKER_COMBO_BUTTON_D_LEFT, "D-Left" }, { TRACKER_COMBO_BUTTON_D_RIGHT, "D-Right" }}; +static std::unordered_map displayTypes = {{ SECTION_DISPLAY_HIDDEN, "Hidden" }, { SECTION_DISPLAY_MAIN_WINDOW, "Main Window" }, { SECTION_DISPLAY_SEPARATE, "Separate" }}; +static std::unordered_map extendedDisplayTypes = {{ SECTION_DISPLAY_EXTENDED_HIDDEN, "Hidden" }, + { SECTION_DISPLAY_EXTENDED_MAIN_WINDOW, "Main Window" }, { SECTION_DISPLAY_EXTENDED_MISC_WINDOW, "Misc Window" }, { SECTION_DISPLAY_EXTENDED_SEPARATE, "Separate" }}; +static std::unordered_map minimalDisplayTypes = {{ SECTION_DISPLAY_MINIMAL_HIDDEN, "Hidden" }, { SECTION_DISPLAY_MINIMAL_SEPARATE, "Separate" }}; void ItemTrackerSettingsWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); ImGui::BeginTable("itemTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); @@ -1370,154 +1383,168 @@ void ItemTrackerSettingsWindow::DrawElement() { ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("BG Color"); - ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::ColorEdit4("BG Color##gItemTrackerBgColor", (float*)&ChromaKeyBackground, ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_NoLabel)) { - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorR"), ChromaKeyBackground.x); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorG"), ChromaKeyBackground.y); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorB"), ChromaKeyBackground.z); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorA"), ChromaKeyBackground.w); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); + CVarColorPicker("Background Color##gItemTrackerBgColor", CVAR_TRACKER_ITEM("BgColor"), { 0, 0, 0, 0 }, true, + ColorPickerRandomButton | ColorPickerResetButton, THEME_COLOR); - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Window Type", CVAR_TRACKER_ITEM("WindowType"), windowTypes, TRACKER_WINDOW_FLOATING)) { + ImGui::PopItemWidth(); + if (CVarCombobox("Window Type", CVAR_TRACKER_ITEM("WindowType"), windowTypes, ComboboxOptions() + .DefaultIndex(TRACKER_WINDOW_FLOATING).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING) { - if (UIWidgets::PaddedEnhancementCheckbox("Enable Dragging", CVAR_TRACKER_ITEM("Draggable"))) { + if (CVarCheckbox("Enable Dragging", CVAR_TRACKER_ITEM("Draggable"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::PaddedEnhancementCheckbox("Only enable while paused", CVAR_TRACKER_ITEM("ShowOnlyPaused"))) { + if (CVarCheckbox("Only enable while paused", CVAR_TRACKER_ITEM("ShowOnlyPaused"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), displayModes, TRACKER_DISPLAY_ALWAYS)) { + if (CVarCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), displayModes, ComboboxOptions() + .DefaultIndex(TRACKER_DISPLAY_ALWAYS).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttons, TRACKER_COMBO_BUTTON_L)) { + if (CVarCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttons, ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_L).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttons, TRACKER_COMBO_BUTTON_R)) { + if (CVarCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttons, ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_R).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } } - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementSliderInt("Icon size : %dpx", "##ITEMTRACKERICONSIZE", CVAR_TRACKER_ITEM("IconSize"), 25, 128, "", 36); - UIWidgets::EnhancementSliderInt("Icon margins : %dpx", "##ITEMTRACKERSPACING", CVAR_TRACKER_ITEM("IconSpacing"), -5, 50, "", 12); - UIWidgets::EnhancementSliderInt("Text size : %dpx", "##ITEMTRACKERTEXTSIZE", CVAR_TRACKER_ITEM("TextSize"), 1, 30, "", 13); + ImGui::Separator(); + CVarSliderInt("Icon size : %dpx", CVAR_TRACKER_ITEM("IconSize"), IntSliderOptions().Min(25).Max(128).DefaultValue(36).Color(THEME_COLOR)); + CVarSliderInt("Icon margins : %dpx", CVAR_TRACKER_ITEM("IconSpacing"), IntSliderOptions().Min(-5).Max(50).DefaultValue(12).Color(THEME_COLOR)); + CVarSliderInt("Text size : %dpx", CVAR_TRACKER_ITEM("TextSize"), IntSliderOptions().Min(1).Max(30).DefaultValue(13).Color(THEME_COLOR)); - UIWidgets::Spacer(0); - - ImGui::Text("Ammo/Capacity Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("ItemCountType"), itemTrackerCapacityTrackOptions, ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY); - UIWidgets::InsertHelpHoverText("Customize what the numbers under each item are tracking." - "\n\nNote: items without capacity upgrades will track ammo even in capacity mode"); + ImGui::NewLine(); + CVarCombobox("Ammo/Capacity Tracking", CVAR_TRACKER_ITEM("ItemCountType"), itemTrackerCapacityTrackOptions, ComboboxOptions() + .DefaultIndex(ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what the numbers under each item are tracking." + "\n\nNote: items without capacity upgrades will track ammo even in capacity mode")); if (CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY) { - if (UIWidgets::PaddedEnhancementCheckbox("Align count to left side", CVAR_TRACKER_ITEM("ItemCountAlignLeft"))) { + if (CVarCheckbox("Align count to left side", CVAR_TRACKER_ITEM("ItemCountAlignLeft"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - UIWidgets::Spacer(0); + CVarCombobox("Key Count Tracking", CVAR_TRACKER_ITEM("KeyCounts"), itemTrackerKeyTrackOptions, ComboboxOptions() + .DefaultIndex(KEYS_COLLECTED_MAX).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what numbers are shown for key tracking.")); - ImGui::Text("Key Count Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("KeyCounts"), itemTrackerKeyTrackOptions, KEYS_COLLECTED_MAX); - UIWidgets::InsertHelpHoverText("Customize what numbers are shown for key tracking."); - - UIWidgets::Spacer(0); - - ImGui::Text("Triforce Piece Count Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("TriforcePieceCounts"), itemTrackerTriforcePieceTrackOptions, TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX); - UIWidgets::InsertHelpHoverText("Customize what numbers are shown for triforce piece tracking."); + CVarCombobox("Triforce Piece Count Tracking", CVAR_TRACKER_ITEM("TriforcePieceCounts"), itemTrackerTriforcePieceTrackOptions, ComboboxOptions() + .DefaultIndex(TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what numbers are shown for triforce piece tracking.")); ImGui::TableNextColumn(); - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == SECTION_DISPLAY_SEPARATE) { - if (UIWidgets::PaddedEnhancementCheckbox("Circle display", CVAR_TRACKER_ITEM("DungeonRewardsLayout"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, false)) { + if (CVarCheckbox("Circle display", CVAR_TRACKER_ITEM("DungeonRewardsLayout"))) { shouldUpdateVectors = true; } } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Dungeon Items", CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Dungeon Items", CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) != SECTION_DISPLAY_HIDDEN) { if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == SECTION_DISPLAY_SEPARATE) { - if (UIWidgets::PaddedEnhancementCheckbox("Horizontal display", CVAR_TRACKER_ITEM("DungeonItems.Layout"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true)) { + if (CVarCheckbox("Horizontal display", CVAR_TRACKER_ITEM("DungeonItems.Layout"), CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - if (UIWidgets::PaddedEnhancementCheckbox("Maps and compasses", CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true)) { + if (CVarCheckbox("Maps and compasses", CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Greg", CVAR_TRACKER_ITEM("DisplayType.Greg"), extendedDisplayTypes, SECTION_DISPLAY_EXTENDED_HIDDEN)) { + if (CVarCombobox("Greg", CVAR_TRACKER_ITEM("DisplayType.Greg"), extendedDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Triforce Pieces", CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Triforce Pieces", CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Boss Souls", CVAR_TRACKER_ITEM("DisplayType.BossSouls"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Boss Souls", CVAR_TRACKER_ITEM("DisplayType.BossSouls"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Ocarina Buttons", CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Ocarina Buttons", CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, SECTION_DISPLAY_EXTENDED_HIDDEN)) { + if (CVarCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Total Checks", "gTrackers.ItemTracker.TotalChecks.DisplayType", minimalDisplayTypes, SECTION_DISPLAY_MINIMAL_HIDDEN)) { + if (CVarCombobox("Total Checks", "gTrackers.ItemTracker.TotalChecks.DisplayType", minimalDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MINIMAL_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_ALWAYS) { - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Personal notes", CVAR_TRACKER_ITEM("DisplayType.Notes"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Personal notes", CVAR_TRACKER_ITEM("DisplayType.Notes"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - UIWidgets::EnhancementCheckbox("Show Hookshot Identifiers", CVAR_TRACKER_ITEM("HookshotIdentifier")); - UIWidgets::InsertHelpHoverText("Shows an 'H' or an 'L' to more easiely distinguish between Hookshot and Longshot."); - - UIWidgets::Spacer(0); + CVarCheckbox("Show Hookshot Identifiers", CVAR_TRACKER_ITEM("HookshotIdentifier"), CheckboxOptions() + .Tooltip("Shows an 'H' or an 'L' to more easiely distinguish between Hookshot and Longshot.").Color(THEME_COLOR)); ImGui::PopStyleVar(1); ImGui::EndTable(); + ImGui::PopFont(); } void ItemTrackerWindow::InitElement() { - float trackerBgR = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorR"), 0); - float trackerBgG = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorG"), 0); - float trackerBgB = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorB"), 0); - float trackerBgA = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorA"), 1); - ChromaKeyBackground = { - trackerBgR, - trackerBgG, - trackerBgB, - trackerBgA - }; // Float value, 1 = 255 in rgb value. // Crashes when the itemTrackerNotes is empty, so add an empty character to it if (itemTrackerNotes.empty()) { itemTrackerNotes.push_back(0); diff --git a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h index 9f297c29d..034101aba 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h +++ b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h @@ -1,3 +1,5 @@ +#pragma once + #include namespace Rando { diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index cf1acdd7f..447da07f9 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -246,15 +246,15 @@ void Settings::CreateOptions() { OPT_U8(RSK_LACS_OPTIONS, "GCBK LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardOptions"), "", WidgetType::Combobox, RO_LACS_STANDARD_REWARD); OPT_U8(RSK_KEYRINGS, "Key Rings", {"Off", "Random", "Count", "Selection"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRings"), mOptionDescriptions[RSK_KEYRINGS], WidgetType::Combobox, RO_KEYRINGS_OFF); OPT_U8(RSK_KEYRINGS_RANDOM_COUNT, "Keyring Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsRandomCount"), "", WidgetType::Slider, 8); - OPT_U8(RSK_KEYRINGS_GERUDO_FORTRESS, "Gerudo Fortress Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_FOREST_TEMPLE, "Forest Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_FIRE_TEMPLE, "Fire Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_WATER_TEMPLE, "Water Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_SPIRIT_TEMPLE, "Spirit Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_SHADOW_TEMPLE, "Shadow Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_BOTTOM_OF_THE_WELL, "Bottom of the Well Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_GTG, "Gerudo Training Ground Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_GANONS_CASTLE, "Ganon's Castle Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle"), "", WidgetType::TristateCheckbox, 0); + OPT_U8(RSK_KEYRINGS_GERUDO_FORTRESS, "Gerudo Fortress Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_FOREST_TEMPLE, "Forest Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_FIRE_TEMPLE, "Fire Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_WATER_TEMPLE, "Water Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_SPIRIT_TEMPLE, "Spirit Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_SHADOW_TEMPLE, "Shadow Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_BOTTOM_OF_THE_WELL, "Bottom of the Well Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_GTG, "Gerudo Training Ground Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_GANONS_CASTLE, "Ganon's Castle Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle"), "", WidgetType::Combobox, 0); //Dummied out due to redundancy with TimeSavers.SkipChildStealth until such a time that logic needs to consider child stealth e.g. because it's freestanding checks are added to freestanding shuffle. //To undo this dummying, readd this setting to an OptionGroup so it appears in the UI, then edit the timesaver check hooks to look at this, and the timesaver setting to lock itself as needed. OPT_BOOL(RSK_SKIP_CHILD_STEALTH, "Skip Child Stealth", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipChildStealth"), mOptionDescriptions[RSK_SKIP_CHILD_STEALTH], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); @@ -1674,7 +1674,7 @@ void Settings::UpdateOptionProperties() { } // Shuffle 100 GS Reward - Force-Enabled if Ganon's Boss Key is on the 100 GS Reward if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) == RO_GANON_BOSS_KEY_KAK_TOKENS) { - mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable("This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\"", UIWidgets::CheckboxGraphics::Checkmark); + mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable("This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\""); } else { mOptions[RSK_SHUFFLE_100_GS_REWARD].Enable(); } diff --git a/soh/soh/Enhancements/randomizer/tricks.cpp b/soh/soh/Enhancements/randomizer/tricks.cpp index 6008ba332..69b3db88d 100644 --- a/soh/soh/Enhancements/randomizer/tricks.cpp +++ b/soh/soh/Enhancements/randomizer/tricks.cpp @@ -1,4 +1,5 @@ #include "tricks.h" +#include "soh/SohGui/UIWidgets.hpp" #include namespace Rando { @@ -71,7 +72,6 @@ namespace Rando { const ImVec4 Tricks::GetTextColor(const Tag tag) { switch(tag) { - case Tag::EXPERIMENTAL: case Tag::GLITCH: return { 0.00f, 0.00f, 0.00f, 1.0f }; default: @@ -82,32 +82,33 @@ namespace Rando { const ImVec4 Tricks::GetTagColor(const Tag tag) { switch(tag) { case Tag::NOVICE: - return { 0.09f, 0.55f, 0.37f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Green); case Tag::INTERMEDIATE: - return { 0.95f, 0.52f, 0.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Orange); case Tag::ADVANCED: - return { 0.00f, 0.29f, 0.71f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Blue); case Tag::EXPERT: - return { 0.53f, 0.05f, 0.14f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Red); case Tag::EXTREME: - return { 0.27f, 0.00f, 0.27f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Purple); case Tag::EXPERIMENTAL: - return { 0.00f, 1.00f, 1.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::LightBlue); case Tag::GLITCH: - return { 1.00f, 1.00f, 1.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::White); default: assert(false); - return { 0.50f, 0.50f, 0.50f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Gray); } } - void Tricks::DrawTagChips(const std::set& rtTags) { + void Tricks::DrawTagChips(const std::set& rtTags, std::string trickName) { for (const Tag rtTag : rtTags) { + std::string tagId = GetTagName(rtTag) + "##" + trickName; ImGui::SameLine(); ImGui::BeginDisabled(); - ImGui::PushStyleColor(ImGuiCol_Button, GetTagColor(rtTag)); - ImGui::SmallButton(GetTagName(rtTag).c_str()); - ImGui::PopStyleColor(); + UIWidgets::PushStyleButton(GetTagColor(rtTag)); + ImGui::SmallButton(tagId.c_str()); + UIWidgets::PopStyleButton(); ImGui::EndDisabled(); } } diff --git a/soh/soh/Enhancements/randomizer/tricks.h b/soh/soh/Enhancements/randomizer/tricks.h index b3b635555..ddaa0f27c 100644 --- a/soh/soh/Enhancements/randomizer/tricks.h +++ b/soh/soh/Enhancements/randomizer/tricks.h @@ -28,7 +28,7 @@ namespace Rando { static const std::string GetTagName(Tag tag); static const ImVec4 GetTextColor(Tag tag); static const ImVec4 GetTagColor(Tag tag); - static void DrawTagChips(const std::set& rtTags); + static void DrawTagChips(const std::set& rtTags, std::string trickName = ""); }; } diff --git a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp b/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp deleted file mode 100644 index a18aea8dc..000000000 --- a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp +++ /dev/null @@ -1,489 +0,0 @@ -#include "ResolutionEditor.h" -#include -#include - -#include "soh/SohGui/UIWidgets.hpp" -#include -#include "soh/OTRGlobals.h" -#include "soh/cvar_prefixes.h" - -/* Console Variables are grouped under gAdvancedResolution. (e.g. CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled") - - The following cvars are used in Libultraship and can be edited here: - - Enabled - Turns Advanced Resolution Mode on. - - AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero. - - VerticalPixelCount, VerticalResolutionToggle - Resolution controls. - - PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls. - - IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode. - - IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds. - - The following cvars are also implemented in LUS for niche use cases: - - IgnoreAspectCorrection - Stretch framebuffer to fill screen. - This is something of a power-user setting for niche setups that most people won't need or care about, - but may be useful if playing the Switch/Wii U ports on a 4:3 television. - - IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1. - This isn't that useful at the moment, so it's unused here. -*/ - -namespace AdvancedResolutionSettings { -enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount }; - -const char* aspectRatioPresetLabels[] = { - "Off", "Custom", "Original (4:3)", "Widescreen (16:9)", "Nintendo 3DS (5:3)", "16:10 (8:5)", "Ultrawide (21:9)" -}; -const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f }; -const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f }; -const int default_aspectRatio = 1; // Default combo list option - -const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)", - "5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" }; -const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 }; -const int default_pixelCount = 0; // Default combo list option - -// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges() -const uint32_t minVerticalPixelCount = SCREEN_HEIGHT; -const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution - -const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider. - -enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 }; -const ImVec4 messageColor[]{ - { 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR - { 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING - { 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION - { 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO - { 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75 -}; -const float enhancementSpacerHeight = 19.0f; - -void AdvancedResolutionSettingsWindow::InitElement() { -} - -void AdvancedResolutionSettingsWindow::DrawElement() { - // Initialise update flags. - bool update[3]; - for (uint8_t i = 0; i < sizeof(update); i++) - update[i] = false; - - // Initialise integer scale bounds. - short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get - // overridden depending on viewport res - - short integerScale_maximumBounds = 1; // can change when window is resized - // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS. - if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) > - ((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) { - // Scale to window height - integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height; - } else { - // Scale to window width - integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width; - } - // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS. - // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded. - if (default_maxIntegerScaleFactor < integerScale_maximumBounds) { - max_integerScaleFactor = - integerScale_maximumBounds + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - } - - // Combo List defaults - static int item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3); - static int item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount); - // Stored Values for non-UIWidgets elements - static float aspectRatioX = - CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]); - static float aspectRatioY = - CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]); - static int verticalPixelCount = - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]); - // Additional settings - static bool showHorizontalResField = false; - static int horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - // Disabling flags - const bool disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); - const bool disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); - -#ifdef __APPLE__ - // Display HiDPI warning. (Remove this once we can definitively say it's fixed.) - ImGui::TextColored(messageColor[MESSAGE_INFO], - ICON_FA_INFO_CIRCLE " These settings may behave incorrectly on Retina displays."); - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); -#endif - - if (ImGui::CollapsingHeader("Original Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - // The original resolution slider (for convenience) - const bool disabled_resolutionSlider = (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0)) || - CVarGetInteger(CVAR_LOW_RES_MODE, 0); - if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %.1f%%", "##IMul", CVAR_INTERNAL_RESOLUTION, 0.5f, - 2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) { - Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( - CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); - } - UIWidgets::Tooltip("Multiplies your output resolution by the value entered."); - - // The original MSAA slider (also for convenience) -#ifndef __WIIU__ - if (UIWidgets::PaddedEnhancementSliderInt("MSAA: %d", "##IMSAA", CVAR_MSAA_VALUE, 1, 8, "", 1, true, true, - false)) { - Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); - }; - UIWidgets::Tooltip( - "Activates multi-sample anti-aliasing when above 1x, up to 8x for 8 samples for every pixel.\n\n" - " " ICON_FA_INFO_CIRCLE - " (Higher MSAA with low resolution can approximate an authentic \"real N64\" look!)"); -#endif - - // N64 Mode toggle (again for convenience) - // UIWidgets::PaddedEnhancementCheckbox("(Enhancements>Graphics) N64 Mode", CVAR_LOW_RES_MODE, false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - } - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - // Activator - UIWidgets::PaddedEnhancementCheckbox("Enable advanced settings.", CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", false, false, - false, "", UIWidgets::CheckboxGraphics::Cross, false); - // Error/Warning display - if (!CVarGetInteger(CVAR_LOW_RES_MODE, 0)) { - if (IsDroppingFrames()) { // Significant frame drop warning - ImGui::TextColored(messageColor[MESSAGE_WARNING], - ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring."); - UIWidgets::Spacer(2); - } else { // No warnings - UIWidgets::Spacer(enhancementSpacerHeight); - } - } else { // N64 Mode warning - ImGui::TextColored(messageColor[MESSAGE_QUESTION], - ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings."); - ImGui::SameLine(); - if (ImGui::Button("Click to disable")) { - CVarSetInteger(CVAR_LOW_RES_MODE, 0); - CVarSave(); - } - } - // Resolution visualiser - ImGui::Text("Viewport dimensions: %d x %d", gfx_current_game_window_viewport.width, - gfx_current_game_window_viewport.height); - ImGui::Text("Internal resolution: %d x %d", gfx_current_dimensions.width, gfx_current_dimensions.height); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - if (disabled_everything) { // Hide aspect ratio controls. - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - // Aspect Ratio - ImGui::Text("Force aspect ratio:"); - ImGui::SameLine(); - ImGui::TextColored(messageColor[MESSAGE_GRAY_75], "(Select \"Off\" to disable.)"); - // Presets - if (ImGui::Combo(" ", &item_aspectRatio, aspectRatioPresetLabels, - IM_ARRAYSIZE(aspectRatioPresetLabels)) && - item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected. - aspectRatioX = aspectRatioPresetsX[item_aspectRatio]; - aspectRatioY = aspectRatioPresetsY[item_aspectRatio]; - - if (showHorizontalResField) { - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); - CVarSave(); - } - // Hide aspect ratio input fields if using one of the presets. - if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) { - // Declare input interaction bools outside of IF statement to prevent Y field from disappearing. - const bool input_X = ImGui::InputFloat("X", &aspectRatioX, 0.1f, 1.0f, "%.3f"); - const bool input_Y = ImGui::InputFloat("Y", &aspectRatioY, 0.1f, 1.0f, "%.3f"); - if (input_X || input_Y) { - item_aspectRatio = default_aspectRatio; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } else if (showHorizontalResField) { // Show calculated aspect ratio - if (item_aspectRatio) { - UIWidgets::Spacer(2); - const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height; - ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio); - } else { - UIWidgets::Spacer(enhancementSpacerHeight); - } - } - - if (disabled_everything) { // Hide aspect ratio controls. - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - UIWidgets::Spacer(0); - - // Vertical Resolution - UIWidgets::PaddedEnhancementCheckbox("Set fixed vertical resolution (disables Resolution slider)", - CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", true, false, - disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip( - "Override the resolution scale slider and use the settings below, irrespective of window size."); - if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels, - IM_ARRAYSIZE(pixelCountPresetLabels)) && - item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected. - verticalPixelCount = pixelCountPresets[item_pixelCount]; - - if (showHorizontalResField) { - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); - CVarSave(); - } - // Horizontal Resolution, if visibility is enabled for it. - if (showHorizontalResField) { - // Only show the field if Aspect Ratio is being enforced. - if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) { - // So basically we're "faking" this one by setting aspectRatioX instead. - if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) { - item_aspectRatio = default_aspectRatio; - if (horizontalPixelCount < SCREEN_WIDTH) { - horizontalPixelCount = SCREEN_WIDTH; - } - aspectRatioX = horizontalPixelCount; - aspectRatioY = verticalPixelCount; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } else { // Display a notice instead. - ImGui::TextColored(messageColor[MESSAGE_QUESTION], - ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required."); - // ImGui::Text(" "); - ImGui::SameLine(); - if (ImGui::Button("Click to resolve")) { - item_aspectRatio = default_aspectRatio; // Set it to Custom - aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults - aspectRatioY = aspectRatioPresetsY[2]; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - } - } - // Vertical Resolution part 2 - if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) { - item_pixelCount = default_pixelCount; - update[UPDATE_verticalPixelCount] = true; - - // Account for the natural instinct to enter horizontal first. - // Ignore vertical resolutions that are below the lower clamp constant. - if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) { - item_aspectRatio = default_aspectRatio; - aspectRatioX = horizontalPixelCount; - aspectRatioY = verticalPixelCount; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } - if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - - UIWidgets::Spacer(0); - - // Integer scaling settings group (Pixel-perfect Mode) - static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag = - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen - : ImGuiTreeNodeFlags_None; - if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) { - const bool disabled_pixelPerfectMode = - !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything; - // Pixel-perfect Mode - UIWidgets::PaddedEnhancementCheckbox("Pixel-perfect Mode", CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", true, - true, disabled_pixelCount || disabled_everything, "", - UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Don't scale image to fill window."); - if (disabled_pixelCount && CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0)) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0); - CVarSave(); - } - - // Integer Scaling - UIWidgets::EnhancementSliderInt( - "Integer scale factor: %d", "##ARSIntScale", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1, - max_integerScaleFactor, "%d", 1, true, - disabled_pixelPerfectMode || CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)); - UIWidgets::Tooltip("Integer scales the image. Only available in pixel-perfect mode."); - // Display warning if size is being clamped or if framebuffer is larger than viewport. - if (!disabled_pixelPerfectMode && - (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1) > integerScale_maximumBounds)) { - ImGui::SameLine(); - ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded."); - } - - UIWidgets::PaddedEnhancementCheckbox( - "Automatically scale image to fit viewport", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", true, - true, disabled_pixelPerfectMode, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode."); - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)) { - // This is just here to update the value shown on the slider. - // The function in LUS to handle this setting will ignore IntegerScaleFactor while active. - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", integerScale_maximumBounds); - // CVarSave(); - } - } // End of integer scaling settings - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Collapsible panel for additional settings - if (ImGui::CollapsingHeader("Additional Settings")) { - UIWidgets::Spacer(0); - -#if defined(__SWITCH__) || defined(__WIIU__) - // Disable aspect correction, stretching the framebuffer to fill the viewport. - // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles. - // The associated cvar is still functional on PC platforms if you want to use it though. - UIWidgets::PaddedEnhancementCheckbox("Disable aspect correction and stretch the output image.\n" - "(Might be useful for 4:3 televisions!)\n" - "Not available in Pixel Perfect Mode.", - CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", false, true, - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || - disabled_everything, - "", UIWidgets::CheckboxGraphics::Cross, false); -#else - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0)) { - // This setting is intentionally not exposed on PC platforms, - // but may be accidentally activated for varying reasons. - // Having this button should hopefully prevent support headaches. - ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE - " If the image is stretched and you don't know why, click this."); - if (ImGui::Button("Click to reenable aspect correction.")) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0); - CVarSave(); - } - UIWidgets::Spacer(2); - } -#endif - - // A requested addition; an alternative way of displaying the resolution field. - if (ImGui::Checkbox("Show a horizontal resolution field, instead of aspect ratio.", &showHorizontalResField)) { - if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off - // Refresh relevant values - aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } else { // when turning this setting on - item_aspectRatio = default_aspectRatio; - if (aspectRatioX > 0.0f) { - // Refresh relevant values in the opposite order - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; - } - } - update[UPDATE_aspectRatioX] = true; - } - - // Beginning of Integer Scaling additional settings. - { - // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Integer Scaling - Never Exceed Bounds. - const bool disabled_neverExceedBounds = - !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0) || disabled_everything; - const bool checkbox_neverExceedBounds = - UIWidgets::PaddedEnhancementCheckbox("Prevent integer scaling from exceeding screen bounds.\n" - "(Makes screen bounds take priority over specified factor.)", - CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", - true, false, disabled_neverExceedBounds, "", - UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "Prevents integer scaling factor from exceeding screen bounds.\n\n" - "Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n" - "Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n" - " " ICON_FA_INFO_CIRCLE - " Please note that exceeding screen bounds may show a scroll bar on-screen."); - - // Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed. - if (checkbox_neverExceedBounds && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - CVarSave(); - } - - // Integer Scaling - Exceed Bounds By 1x/Offset. - // A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p 5x". - /* - UIWidgets::PaddedEnhancementCheckbox("Allow integer scale factor to go +1 above maximum screen bounds.", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", false, false, !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false); - */ - // It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar. - // I've ended up commenting this one out because of the scroll bar, and for simplicity. - - // Display an info message about the scroll bar. - if (!CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) || - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - if (disabled_neverExceedBounds) { // Dim this help text accordingly - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - ImGui::TextColored(messageColor[MESSAGE_INFO], - " " ICON_FA_INFO_CIRCLE - " A scroll bar may become visible if screen bounds are exceeded."); - if (disabled_neverExceedBounds) { // Dim this help text accordingly - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - - // Another support helper button, to disable the unused "Exceed Bounds By" cvar. - // (Remove this button if uncommenting the checkbox.) - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - if (ImGui::Button("Click to reset a console variable that may be causing this.")) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - CVarSave(); - } - } - } else { - ImGui::Text(" "); - } - // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - } // End of Integer Scaling additional settings. - - } // End of additional settings - - // Clamp and update the cvars that don't use UIWidgets - if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { - if (update[UPDATE_aspectRatioX]) { - if (aspectRatioX < 0.0f) { - aspectRatioX = 0.0f; - } - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); - } - if (update[UPDATE_aspectRatioY]) { - if (aspectRatioY < 0.0f) { - aspectRatioY = 0.0f; - } - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); - } - if (update[UPDATE_verticalPixelCount]) { - // There's a upper and lower clamp on the Libultraship side too, - // so clamping it here is entirely visual, so the vertical resolution field reflects it. - if (verticalPixelCount < minVerticalPixelCount) { - verticalPixelCount = minVerticalPixelCount; - } - if (verticalPixelCount > maxVerticalPixelCount) { - verticalPixelCount = maxVerticalPixelCount; - } - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); - } - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); - CVarSave(); - } -} - -void AdvancedResolutionSettingsWindow::UpdateElement() { -} - -bool AdvancedResolutionSettingsWindow::IsDroppingFrames() { - // a rather imprecise way of checking for frame drops. - // but it's mostly there to inform the player of large drops. - const short targetFPS = CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); - const float threshold = targetFPS / 20.0f + 4.1f; - return ImGui::GetIO().Framerate < targetFPS - threshold; -} -} // namespace AdvancedResolutionSettings diff --git a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h b/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h deleted file mode 100644 index f75931451..000000000 --- a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include - -namespace AdvancedResolutionSettings { -class AdvancedResolutionSettingsWindow : public Ship::GuiWindow { - private: - bool IsDroppingFrames(); - - public: - using GuiWindow::GuiWindow; - - void InitElement() override; - void DrawElement() override; - void UpdateElement() override; -}; -} // namespace AdvancedResolutionSettings diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index de9e157c3..4340be330 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -1,5 +1,4 @@ #include "TimeSplits.h" -#include "soh/SohGui/UIWidgets.hpp" #include "soh/Enhancements/gameplaystats.h" #include "soh/SaveManager.h" #include "soh/util.h" @@ -14,12 +13,16 @@ #include "soh/Enhancements/debugger/debugSaveEditor.h" #include "soh_assets.h" #include "assets/textures/parameter_static/parameter_static.h" +#include +#include "soh/SohGui/UIWidgets.hpp" extern "C" { extern SaveContext gSaveContext; extern PlayState* gPlayState; } +using namespace UIWidgets; + // ImVec4 Colors #define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f) #define COLOR_LIGHT_RED ImVec4(1.0f, 0.05f, 0.0f, 1.0f) @@ -636,59 +639,60 @@ void TimeSplitsDrawSplitsList() { uint32_t dragIndex = 0; ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); - ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable); - ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); - ImGui::TableSetupColumn("Item Name"); - ImGui::TableSetupColumn("Current Time"); - ImGui::TableSetupColumn("+/-"); - ImGui::TableSetupColumn("Prev. Best"); - ImGui::TableHeadersRow(); + if (ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable)) { + ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); + ImGui::TableSetupColumn("Item Name"); + ImGui::TableSetupColumn("Current Time"); + ImGui::TableSetupColumn("+/-"); + ImGui::TableSetupColumn("Prev. Best"); + ImGui::TableHeadersRow(); - SplitsPushImageButtonStyle(); - for (auto& split : splitList) { - ImGui::TableNextColumn(); - TimeSplitsSplitBestTimeDisplay(split); - - ImGui::PushID(split.splitID); - if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); + SplitsPushImageButtonStyle(); + for (auto& split : splitList) { + ImGui::TableNextColumn(); + TimeSplitsSplitBestTimeDisplay(split); + + ImGui::PushID(split.splitID); + if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); + } + TimeSplitsGetImageSize(split.splitID); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); + auto ret = ImGui::ImageButton(split.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), + imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); + ImGui::PopStyleVar(); + if (ret) { + TimeSplitsSkipSplit(dragIndex); + } + HandleDragAndDrop(splitList, dragIndex, split.splitName); + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", split.splitName.c_str()); + ImGui::TableNextColumn(); + // Current Time + ImGui::Text("%s", (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) + ? formatTimestampTimeSplit(GAMEPLAYSTAT_TOTAL_TIME).c_str() : (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) + ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); + ImGui::TableNextColumn(); + // +/- Difference + ImGui::TextColored(splitTimeColor, "%s", formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); + ImGui::TableNextColumn(); + // Previous Best + ImGui::Text("%s", (split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); + ImGui::PopID(); + ImGui::PopStyleVar(1); + + dragIndex++; } - TimeSplitsGetImageSize(split.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(split.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), - imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); - ImGui::PopStyleVar(); - if (ret) { - TimeSplitsSkipSplit(dragIndex); - } - HandleDragAndDrop(splitList, dragIndex, split.splitName); - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); - ImGui::AlignTextToFramePadding(); - ImGui::TextWrapped("%s", split.splitName.c_str()); - ImGui::TableNextColumn(); - // Current Time - ImGui::Text("%s", (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) - ? formatTimestampTimeSplit(GAMEPLAYSTAT_TOTAL_TIME).c_str() : (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) - ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); - ImGui::TableNextColumn(); - // +/- Difference - ImGui::TextColored(splitTimeColor, "%s", formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); - ImGui::TableNextColumn(); - // Previous Best - ImGui::Text("%s", (split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); - ImGui::PopID(); - ImGui::PopStyleVar(1); + SplitsPopImageButtonStyle(); - dragIndex++; + TimeSplitsPostDragAndDrop(); + + ImGui::EndTable(); } - SplitsPopImageButtonStyle(); - - TimeSplitsPostDragAndDrop(); - - ImGui::PopStyleVar(1); - ImGui::EndTable(); + ImGui::PopStyleVar(); ImGui::EndChild(); } @@ -769,7 +773,7 @@ void TimeSplitsDrawItemList(uint32_t type) { } void TimeSplitsUpdateWindowSize() { - timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 0); + timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowScale"), 0); if (timeSplitsWindowSize < 1.0f) { timeSplitsWindowSize = 1.0f; } @@ -777,24 +781,13 @@ void TimeSplitsUpdateWindowSize() { void TimeSplitsDrawOptionsMenu() { ImGui::SeparatorText("Window Options"); - if (ImGui::ColorEdit4("Background Color", (float*)&windowColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) { - Color_RGBA8 color; - color.r = windowColor.x * 255.0; - color.g = windowColor.y * 255.0; - color.b = windowColor.z * 255.0; - color.a = windowColor.w * 255.0; - CVarSetColor("TimeSplits.WindowColor", color); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::SameLine(); - if (ImGui::Button("Reset")) { - windowColor = { 0.0f, 0.0f, 0.0f, 1.0f }; - CVarSetColor("TimeSplits.WindowColor", {0, 0, 0, 1}); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Color_RGBA8 defaultColor = { 0, 0, 0, 255 }; + if (CVarColorPicker("Background Color", CVAR_ENHANCEMENT("TimeSplits.WindowColor"), defaultColor, true, 0, THEME_COLOR)) { + windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColor)); } - if (UIWidgets::PaddedEnhancementSliderFloat("Window Size: %.1fx", "##windowSize", - CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, true, true, false)) { + if (CVarSliderFloat("Window Scale", CVAR_ENHANCEMENT("TimeSplits.WindowScale"), + FloatSliderOptions().Min(1.0f).Max(3.0f).DefaultValue(1.0f).Format("%.1fx").Size({300.0f, 0.0f}).Step(0.1f).Color(THEME_COLOR))) { TimeSplitsUpdateWindowSize(); } @@ -802,55 +795,44 @@ void TimeSplitsDrawOptionsMenu() { ImGui::Text("New List Name: "); ImGui::PushItemWidth(150.0f); + PushStyleInput(THEME_COLOR); ImGui::InputText("##listName", listNameBuf, 25); + PopStyleInput(); ImGui::PopItemWidth(); ImGui::SameLine(); - if (ImGui::Button("Create List")) { + if (Button("Create List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_SAVE, listNameBuf, splitList); } UIWidgets::PaddedSeparator(); TimeSplitsFileManagement(SPLIT_ACTION_COLLECT, "", emptyList); static uint32_t selectedItem = 0; - static std::string listItem = keys[0]; ImGui::Text("Select List to Load: "); ImGui::PushItemWidth(150.0f); - if (ImGui::BeginCombo("##listEntries", keys[selectedItem].c_str())) { - for (int i = 0; i < keys.size(); i++) { - bool isSelected = (selectedItem == i); - if (ImGui::Selectable(keys[i].c_str(), isSelected)) { - selectedItem = i; - listItem = keys[i].c_str(); - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } + Combobox("", &selectedItem, keys, ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPosition::Near)); ImGui::PopItemWidth(); ImGui::SameLine(); - if (ImGui::Button("Load List")) { + if (Button("Load List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_LOAD, keys[selectedItem].c_str(), emptyList); } ImGui::SameLine(); - if (ImGui::Button("Save List")) { + if (Button("Save List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_SAVE, keys[selectedItem].c_str(), splitList); } ImGui::SameLine(); - if (ImGui::Button("Delete List")) { + if (Button("Delete List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_DELETE, keys[selectedItem].c_str(), emptyList); } - UIWidgets::PaddedSeparator(); + UIWidgets::Separator(true, true, ImGui::GetStyle().ItemSpacing.y, ImGui::GetStyle().ItemSpacing.y); - if (ImGui::Button("New Attempt")) { + if (Button("New Attempt", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { for (auto& data : splitList) { data.splitTimeStatus = SPLIT_STATUS_INACTIVE; } splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; } ImGui::SameLine(); - if (ImGui::Button("Update Splits")) { + if (Button("Update Splits", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_UPDATE, keys[selectedItem].c_str(), splitList); } } @@ -864,75 +846,80 @@ void TimeSplitsRemoveSplitEntry(uint32_t index) { void TimeSplitsDrawManageList() { uint32_t index = 0; - ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); + if (ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Preview"); - if (ImGui::BeginTabItem("Preview")) { - ImGui::BeginChild("PreviewChild"); - for (auto& data : splitList) { - float availableWidth = ImGui::GetContentRegionAvail().x; - float imageWidth = 38.0f; // Width of your image button - float offsetX = (availableWidth - imageWidth) * 0.5f; // Centering offset + ImGui::TableNextColumn(); + ImGui::BeginTabBar("List Preview"); + if (ImGui::BeginTabItem("Preview")) { + ImGui::BeginChild("PreviewChild"); + for (auto& data : splitList) { + float availableWidth = ImGui::GetContentRegionAvail().x; + float imageWidth = 38.0f; // Width of your image button + float offsetX = (availableWidth - imageWidth) * 0.5f; // Centering offset - if (offsetX > 0.0f) { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center + if (offsetX > 0.0f) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center + } + TimeSplitsGetImageSize(data.splitID); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); + auto ret = ImGui::ImageButton(data.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), + imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), data.splitTint); + ImGui::PopStyleVar(); + if (ret) { + removeIndex = index; + } + HandleDragAndDrop(splitList, index, splitList[index].splitName); + index++; } - TimeSplitsGetImageSize(data.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(data.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), - imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), data.splitTint); - ImGui::PopStyleVar(); - if (ret) { - removeIndex = index; - } - HandleDragAndDrop(splitList, index, splitList[index].splitName); - index++; + TimeSplitsRemoveSplitEntry(removeIndex); + ImGui::EndChild(); + ImGui::EndTabItem(); } - TimeSplitsRemoveSplitEntry(removeIndex); - ImGui::EndChild(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - - ImGui::PopStyleColor(3); - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Options"); - if (ImGui::BeginTabItem("Equipment")) { - TimeSplitsDrawItemList(SPLIT_TYPE_EQUIPMENT); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Inventory")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ITEM); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Quest")) { - TimeSplitsDrawItemList(SPLIT_TYPE_QUEST); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Entrances")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ENTRANCE); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Bosses")) { - TimeSplitsDrawItemList(SPLIT_TYPE_BOSS); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Miscellaneous")) { - TimeSplitsDrawItemList(SPLIT_TYPE_MISC); - ImGui::EndTabItem(); - } + ImGui::EndTabBar(); - TimeSplitsPostDragAndDrop(); + ImGui::PopStyleColor(3); + ImGui::TableNextColumn(); + ImGui::BeginTabBar("List Options"); + if (ImGui::BeginTabItem("Equipment")) { + TimeSplitsDrawItemList(SPLIT_TYPE_EQUIPMENT); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Inventory")) { + TimeSplitsDrawItemList(SPLIT_TYPE_ITEM); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Quest")) { + TimeSplitsDrawItemList(SPLIT_TYPE_QUEST); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Entrances")) { + TimeSplitsDrawItemList(SPLIT_TYPE_ENTRANCE); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Bosses")) { + TimeSplitsDrawItemList(SPLIT_TYPE_BOSS); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Miscellaneous")) { + TimeSplitsDrawItemList(SPLIT_TYPE_MISC); + ImGui::EndTabItem(); + } - ImGui::EndTabBar(); - ImGui::EndTable(); + TimeSplitsPostDragAndDrop(); + + ImGui::EndTabBar(); + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::EndChild(); } void InitializeSplitDataFile() { @@ -954,14 +941,9 @@ static bool initialized = false; void TimeSplitWindow::DrawElement() { ImGui::SetWindowFontScale(timeSplitsWindowSize); - if (!initialized) { - Color_RGBA8 defaultColour = {0, 0, 0, 255}; - Color_RGBA8 color = CVarGetColor("TimeSplits.WindowColor", defaultColour); - windowColor = {(float)color.r / 255.0f, (float)color.g / 255.0f, (float)color.b / 255.0f, (float)color.a / 255.0f}; - InitializeSplitDataFile(); - initialized = true; - } + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Split Tabs")) { if (ImGui::BeginTabItem("Splits")) { TimeSplitsDrawSplitsList(); @@ -977,6 +959,8 @@ void TimeSplitWindow::DrawElement() { } ImGui::EndTabBar(); } + PopStyleTabs(); + ImGui::PopFont(); } void TimeSplitWindow::InitElement() { @@ -984,6 +968,9 @@ void TimeSplitWindow::InitElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_TRIFORCE_PIECE_WHITE", gWTriforcePieceTex, ImVec4(1, 1, 1, 1)); Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_SPLIT_ENTRANCE", gSplitEntranceTex, ImVec4(1, 1, 1, 1)); + Color_RGBA8 defaultColour = {0, 0, 0, 255}; + windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColour)); + InitializeSplitDataFile(); GameInteractor::Instance->RegisterGameHook([](u8 item) { if (item != ITEM_SKULL_TOKEN) { diff --git a/soh/soh/Network/CrowdControl/CrowdControl.cpp b/soh/soh/Network/CrowdControl/CrowdControl.cpp index 184d6cad2..34532f73b 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.cpp +++ b/soh/soh/Network/CrowdControl/CrowdControl.cpp @@ -764,69 +764,4 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { return effect; } - -void CrowdControl::DrawMenu() { - ImGui::PushID("CrowdControl"); - - static std::string host = CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"); - static uint16_t port = CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384); - bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535; - - ImGui::SeparatorText("Crowd Control"); - UIWidgets::Tooltip( - "Crowd Control is a platform that allows viewers to interact " - "with a streamer's game in real time.\n" - "\n" - "Click the question mark to copy the link to the Crowd Control " - "website to your clipboard." - ); - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText("https://crowdcontrol.live"); - } - - ImGui::BeginDisabled(isEnabled); - ImGui::Text("Host & Port"); - if (UIWidgets::InputString("##Host", &host)) { - CVarSetString(CVAR_REMOTE_CROWD_CONTROL("Host"), host.c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetFontSize() * 5); - if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) { - CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), port); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); - ImGui::EndDisabled(); - - ImGui::Spacing(); - - ImGui::BeginDisabled(!isFormValid); - const char* buttonLabel = isEnabled ? "Disable" : "Enable"; - if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) { - if (isEnabled) { - CVarClear(CVAR_REMOTE_CROWD_CONTROL("Enabled")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Disable(); - } else { - CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Enable(); - } - } - ImGui::EndDisabled(); - - if (isEnabled) { - ImGui::Spacing(); - if (isConnected) { - ImGui::Text("Connected"); - } else { - ImGui::Text("Connecting..."); - } - } - - ImGui::PopID(); -} - #endif diff --git a/soh/soh/Network/CrowdControl/CrowdControl.h b/soh/soh/Network/CrowdControl/CrowdControl.h index 534197807..8aff1c96e 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.h +++ b/soh/soh/Network/CrowdControl/CrowdControl.h @@ -84,7 +84,6 @@ class CrowdControl : public Network { void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); - void DrawMenu(); }; #endif // __cplusplus diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index f9ae7b313..272214115 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -501,72 +501,4 @@ void Sail::RegisterHooks() { }); } -void Sail::DrawMenu() { - ImGui::PushID("Sail"); - - static std::string host = CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"); - static uint16_t port = CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384); - bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535; - - ImGui::SeparatorText("Sail"); - UIWidgets::Tooltip( - "Sail is a networking protocol designed to facilitate remote " - "control of the Ship of Harkinian client. It is intended to " - "be utilized alongside a Sail server, for which we provide a " - "few straightforward implementations on our GitHub. The current " - "implementations available allow integration with Twitch chat " - "and SAMMI Bot, feel free to contribute your own!\n" - "\n" - "Click the question mark to copy the link to the Sail Github " - "page to your clipboard." - ); - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText("https://github.com/HarbourMasters/sail"); - } - - ImGui::BeginDisabled(isEnabled); - ImGui::Text("Host & Port"); - if (UIWidgets::InputString("##Host", &host)) { - CVarSetString(CVAR_REMOTE_SAIL("Host"), host.c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetFontSize() * 5); - if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) { - CVarSetInteger(CVAR_REMOTE_SAIL("Port"), port); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); - ImGui::EndDisabled(); - - ImGui::Spacing(); - - ImGui::BeginDisabled(!isFormValid); - const char* buttonLabel = isEnabled ? "Disable" : "Enable"; - if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) { - if (isEnabled) { - CVarClear(CVAR_REMOTE_SAIL("Enabled")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Disable(); - } else { - CVarSetInteger(CVAR_REMOTE_SAIL("Enabled"), 1); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Enable(); - } - } - ImGui::EndDisabled(); - - if (isEnabled) { - ImGui::Spacing(); - if (isConnected) { - ImGui::Text("Connected"); - } else { - ImGui::Text("Connecting..."); - } - } - - ImGui::PopID(); -} - #endif // ENABLE_REMOTE_CONTROL diff --git a/soh/soh/Network/Sail/Sail.h b/soh/soh/Network/Sail/Sail.h index 8c7c9d55e..2a41b8723 100644 --- a/soh/soh/Network/Sail/Sail.h +++ b/soh/soh/Network/Sail/Sail.h @@ -18,7 +18,6 @@ class Sail : public Network { void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); - void DrawMenu(); }; #endif // __cplusplus diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 2acc01d7f..0b8d0030a 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -40,6 +40,7 @@ #include "z64.h" #include "macros.h" #include "Fonts.h" +#include "window/gui/resource/Font.h" #include #include "Enhancements/custom-message/CustomMessageManager.h" #include "Enhancements/presets.h" @@ -345,7 +346,7 @@ OTRGlobals::OTRGlobals() { context->InitCrashHandler(); context->InitConsole(); - auto sohInputEditorWindow = std::make_shared(CVAR_WINDOW("ControllerConfiguration"), "Controller Configuration"); + auto sohInputEditorWindow = std::make_shared(CVAR_WINDOW("ControllerConfiguration"), "Configure Controller"); auto sohFast3dWindow = std::make_shared(std::vector>({sohInputEditorWindow})); context->InitWindow(sohFast3dWindow); @@ -399,9 +400,14 @@ OTRGlobals::OTRGlobals() { hasMasterQuest = hasOriginal = false; previousImGuiScale = defaultImGuiScale; - defaultFontSmaller = CreateDefaultFontWithSize(10.0f); - defaultFontLarger = CreateDefaultFontWithSize(16.0f); - defaultFontLargest = CreateDefaultFontWithSize(20.0f); + + fontMono = CreateFontWithSize(16.0f, "fonts/Inconsolata-Regular.ttf"); + fontMonoLarger = CreateFontWithSize(20.0f, "fonts/Inconsolata-Regular.ttf"); + fontMonoLargest = CreateFontWithSize(24.0f, "fonts/Inconsolata-Regular.ttf"); + fontStandard = CreateFontWithSize(16.0f, "fonts/Montserrat-Regular.ttf"); + fontStandardLarger = CreateFontWithSize(20.0f, "fonts/Montserrat-Regular.ttf"); + fontStandardLargest = CreateFontWithSize(24.0f, "fonts/Montserrat-Regular.ttf"); + ImGui::GetIO().FontDefault = fontMono; ScaleImGui(); // Move the camera strings from read only memory onto the heap (writable memory) @@ -1390,9 +1396,12 @@ void RunCommands(Gfx* Commands, const std::vector // Process window events for resize, mouse, keyboard events wnd->HandleEvents(); + UIWidgets::Colors themeColor = static_cast(CVarGetInteger(CVAR_SETTING("Menu.Theme"), UIWidgets::Colors::LightBlue)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIWidgets::ColorValues.at(themeColor)); for (const auto& m : mtx_replacements) { wnd->DrawAndRunGraphicsCommands(Commands, m); } + ImGui::PopStyleColor(); } // C->C++ Bridge @@ -1559,6 +1568,37 @@ extern "C" SoundFontSample* ReadCustomSample(const char* path) { */ } +ImFont* OTRGlobals::CreateFontWithSize(float size, std::string fontPath) { + auto mImGuiIo = &ImGui::GetIO(); + ImFont* font; + if (fontPath == "") { + ImFontConfig fontCfg = ImFontConfig(); + fontCfg.OversampleH = fontCfg.OversampleV = 1; + fontCfg.PixelSnapH = true; + fontCfg.SizePixels = size; + font = mImGuiIo->Fonts->AddFontDefault(&fontCfg); + } else { + auto initData = std::make_shared(); + initData->Format = RESOURCE_FORMAT_BINARY; + initData->Type = static_cast(RESOURCE_TYPE_FONT); + initData->ResourceVersion = 0; + initData->Path = fontPath; + std::shared_ptr fontData = std::static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResource(fontPath, false, initData)); + font = mImGuiIo->Fonts->AddFontFromMemoryTTF(fontData->Data, fontData->DataSize, size); + } + // FontAwesome fonts need to have their sizes reduced by 2.0f/3.0f in order to align correctly + float iconFontSize = size * 2.0f / 3.0f; + static const ImWchar sIconsRanges[] = { ICON_MIN_FA, ICON_MAX_16_FA, 0 }; + ImFontConfig iconsConfig; + iconsConfig.MergeMode = true; + iconsConfig.PixelSnapH = true; + iconsConfig.GlyphMinAdvanceX = iconFontSize; + mImGuiIo->Fonts->AddFontFromMemoryCompressedBase85TTF(fontawesome_compressed_data_base85, iconFontSize, + &iconsConfig, sIconsRanges); + return font; +} + std::filesystem::path GetSaveFile(std::shared_ptr Conf) { const std::string fileName = Conf->GetString("Game.SaveName", Ship::Context::GetPathRelativeToAppDirectory("oot_save.sav")); std::filesystem::path saveFile = std::filesystem::absolute(fileName); diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index f056cad5e..0a8d6595e 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -54,6 +54,13 @@ class OTRGlobals { ImFont* defaultFontLarger; ImFont* defaultFontLargest; + ImFont* fontStandard; + ImFont* fontStandardLarger; + ImFont* fontStandardLargest; + ImFont* fontMono; + ImFont* fontMonoLarger; + ImFont* fontMonoLargest; + OTRGlobals(); ~OTRGlobals(); @@ -69,6 +76,7 @@ class OTRGlobals { bool hasMasterQuest; bool hasOriginal; ImFont* CreateDefaultFontWithSize(float size); + ImFont* CreateFontWithSize(float size, std::string fontPath); }; #endif diff --git a/soh/soh/ShipUtils.cpp b/soh/soh/ShipUtils.cpp new file mode 100644 index 000000000..4760dbcd0 --- /dev/null +++ b/soh/soh/ShipUtils.cpp @@ -0,0 +1,115 @@ +#include "ShipUtils.h" +#include + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" + +extern float OTRGetAspectRatio(); + +//extern f32 sNESFontWidths[160]; +extern const char* fontTbl[156]; +//extern TexturePtr gItemIcons[131]; +//extern TexturePtr gQuestIcons[14]; +//extern TexturePtr gBombersNotebookPhotos[24]; +} + +constexpr f32 fourByThree = 4.0f / 3.0f; + +// Gets the additional ratio of the screen compared to the original 4:3 ratio, clamping to 1 if smaller +extern "C" f32 Ship_GetExtendedAspectRatioMultiplier() { + f32 currentRatio = OTRGetAspectRatio(); + return MAX(currentRatio / fourByThree, 1.0f); +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the draw distance slider +// to the projected Z value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor) { + s32 multiplier = CVarGetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", 1); + multiplier = MAX(multiplier, 1); + if (multiplier > 1) { + actor->projectedPos.z /= multiplier; + } +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the widescreen aspect ratio +// to the projected X value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor) { + if (CVarGetInteger("gEnhancements.Graphics.ActorCullingAccountsForWidescreen", 0)) { + f32 ratioAdjusted = Ship_GetExtendedAspectRatioMultiplier(); + actor->projectedPos.x /= ratioAdjusted; + } +} + +// Restores the projectedPos values on the actor after modifications from the Extended Culling hacks +//extern "C" void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor) { +// f32 invW = 0.0f; +// Actor_GetProjectedPos(play, &actor->world.pos, &actor->projectedPos, &invW); +//} + +extern "C" bool Ship_IsCStringEmpty(const char* str) { + return str == NULL || str[0] == '\0'; +} + +// Build vertex coordinates for a quad command +// In order of top left, top right, bottom left, then bottom right +// Supports flipping the texture horizontally +extern "C" void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH) { + vtxList[0].v.ob[0] = xStart; + vtxList[0].v.ob[1] = yStart; + vtxList[0].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[0].v.tc[1] = 0 << 5; + + vtxList[1].v.ob[0] = xStart + width; + vtxList[1].v.ob[1] = yStart; + vtxList[1].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[1].v.tc[1] = 0 << 5; + + vtxList[2].v.ob[0] = xStart; + vtxList[2].v.ob[1] = yStart + height; + vtxList[2].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[2].v.tc[1] = height << 5; + + vtxList[3].v.ob[0] = xStart + width; + vtxList[3].v.ob[1] = yStart + height; + vtxList[3].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[3].v.tc[1] = height << 5; +} + +//extern "C" f32 Ship_GetCharFontWidthNES(u8 character) { +// u8 adjustedChar = character - ' '; +// +// if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { +// return 0.0f; +// } +// +// return sNESFontWidths[adjustedChar]; +//} + +//extern "C" TexturePtr Ship_GetCharFontTextureNES(u8 character) { +// u8 adjustedChar = character - ' '; +// +// if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { +// return (TexturePtr)gEmptyTexture; +// } +// +// return (TexturePtr)fontTbl[adjustedChar]; +//} + +//void LoadGuiTextures() { +// for (TexturePtr entry : gItemIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gQuestIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gBombersNotebookPhotos) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +//} diff --git a/soh/soh/ShipUtils.h b/soh/soh/ShipUtils.h new file mode 100644 index 000000000..a1ac3081b --- /dev/null +++ b/soh/soh/ShipUtils.h @@ -0,0 +1,31 @@ +#ifndef SHIP_UTILS_H +#define SHIP_UTILS_H + +#include +//#include "PR/ultratypes.h" + +#ifdef __cplusplus + +void LoadGuiTextures(); + +extern "C" { +#endif + +struct PlayState; +struct Actor; + +f32 Ship_GetExtendedAspectRatioMultiplier(); +void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor); +void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor); +void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor); + +bool Ship_IsCStringEmpty(const char* str); +void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH); +f32 Ship_GetCharFontWidthNES(u8 character); +//TexturePtr Ship_GetCharFontTextureNES(u8 character); + +#ifdef __cplusplus +} +#endif + +#endif // SHIP_UTILS_H diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp new file mode 100644 index 000000000..a10fc4e06 --- /dev/null +++ b/soh/soh/SohGui/Menu.cpp @@ -0,0 +1,819 @@ +#include "Menu.h" +#include "UIWidgets.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include +#include +#include "variables.h" +#include + +extern "C" { +#include "z64.h" +#include "functions.h" +extern PlayState* gPlayState; +} +std::vector windowTypeSizes = { {} }; + +extern std::unordered_map warpPointSceneList; +extern void Warp(); + +namespace SohGui {} + +namespace Ship { +std::string disabledTempTooltip; +const char* disabledTooltip; +bool disabledValue = false; + +bool operator==(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b; +} + +bool operator==(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b && l.a == r.a; +} + +bool operator<(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b) || (l.r <= r.r && l.g < r.g && l.b <= r.b) || + (l.r <= r.r && l.g <= r.g && l.b < r.b); +} + +bool operator<(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g < r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b < r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b <= r.b && l.a < r.a); +} + +bool operator>(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b) || (l.r >= r.r && l.g > r.g && l.b >= r.b) || + (l.r >= r.r && l.g >= r.g && l.b > r.b); +} + +bool operator>(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g > r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b > r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b >= r.b && l.a > r.a); +} + +uint32_t GetVectorIndexOf(std::vector& vector, std::string value) { + return std::distance(vector.begin(), std::find(vector.begin(), vector.end(), value)); +} + +void Menu::InsertSidebarSearch() { + menuEntries["Settings"].sidebars.emplace("Search", searchSidebarEntry); + uint32_t curIndex = 0; + if (!Ship_IsCStringEmpty(CVarGetString(menuEntries["Settings"].sidebarCvar, ""))) { + curIndex = GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, + CVarGetString(menuEntries["Settings"].sidebarCvar, "")); + } + menuEntries["Settings"].sidebarOrder.insert(menuEntries["Settings"].sidebarOrder.begin() + searchSidebarIndex, + "Search"); + if (curIndex > searchSidebarIndex) { + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); + } +} + +void Menu::RemoveSidebarSearch() { + uint32_t curIndex = + GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, CVarGetString(menuEntries["Settings"].sidebarCvar, "General")); + menuEntries["Settings"].sidebars.erase("Search"); + std::erase_if(menuEntries["Settings"].sidebarOrder, [](std::string& name) { return name == "Search"; }); + if (curIndex > searchSidebarIndex) { + curIndex--; + } else if (curIndex >= menuEntries["Settings"].sidebarOrder.size()) { + curIndex = menuEntries["Settings"].sidebarOrder.size() - 1; + } + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); +} + +void Menu::UpdateWindowBackendObjects() { + Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend(); + int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1); + if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) { + configWindowBackend = static_cast(configWindowBackendId); + } else { + configWindowBackend = runningWindowBackend; + } + + availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends(); + for (auto& backend : *availableWindowBackends) { + availableWindowBackendsMap[backend] = windowBackendsMap.at(backend); + } +} + +UIWidgets::Colors Menu::GetMenuThemeColor() { + return menuThemeIndex; +} + +Menu::Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_, + UIWidgets::Colors defaultThemeIndex_) + : GuiWindow(cVar, name), searchSidebarIndex(searchSidebarIndex_), defaultThemeIndex(defaultThemeIndex_) { +} + +void Menu::InitElement() { + popped = CVarGetInteger(CVAR_SETTING("Menu.Popout"), 0); + poppedSize.x = CVarGetInteger(CVAR_SETTING("Menu.PoppedWidth"), 1280); + poppedSize.y = CVarGetInteger(CVAR_SETTING("Menu.PoppedHeight"), 800); + poppedPos.x = CVarGetInteger(CVAR_SETTING("Menu.PoppedPos.x"), 0); + poppedPos.y = CVarGetInteger(CVAR_SETTING("Menu.PoppedPos.y"), 0); + + UpdateWindowBackendObjects(); +} + +void Menu::UpdateElement() { + menuThemeIndex = static_cast(CVarGetInteger(CVAR_SETTING("Menu.Theme"), defaultThemeIndex)); +} + +bool ModernMenuSidebarEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID sidebarId = window->GetID(std::string(label + "##Sidebar").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + pos.y += style.FramePadding.y; + pos.x = window->WorkRect.GetCenter().x - labelSize.x / 2; + ImRect bb = { pos - style.FramePadding, pos + labelSize + style.FramePadding }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, sidebarId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, sidebarId, &hovered, &held); + if (pressed) { + ImGui::MarkItemEdited(sidebarId); + } + window->DrawList->AddRectFilled(pos - style.FramePadding, pos + labelSize + style.FramePadding, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +bool ModernMenuHeaderEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID headerId = window->GetID(std::string(label + "##Header").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + ImRect bb = { pos, pos + labelSize + style.FramePadding * 2 }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, headerId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, headerId, &hovered, &held); + window->DrawList->AddRectFilled(bb.Min, bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + pos += style.FramePadding; + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +uint32_t Menu::DrawSearchResults(std::string& menuSearchText) { + ImGui::BeginChild("Search Results"); + int searchCount = 0; + for (auto& menuLabel : menuOrder) { + auto& menuEntry = menuEntries.at(menuLabel); + for (auto& sidebarLabel : menuEntry.sidebarOrder) { + auto& sidebar = menuEntry.sidebars[sidebarLabel]; + for (int i = 0; i < sidebar.columnWidgets.size(); i++) { + auto& column = sidebar.columnWidgets.at(i); + for (auto& info : column) { + if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || info.type == WIDGET_SEPARATOR_TEXT || + info.isHidden) { + continue; + } + const char* tooltip = info.options->tooltip; + std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : ""); + std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower); + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), + menuSearchText.end()); + std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower); + widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end()); + if (widgetStr.find(menuSearchText) != std::string::npos) { + MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray)); + std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + searchCount++; + } + } + } + } + } + return searchCount; +} + +void Menu::AddMenuEntry(std::string entryName, const char* entryCvar) { + menuEntries.emplace(entryName, MainMenuEntry{ entryName, entryCvar }); + menuOrder.push_back(entryName); +} + +std::unordered_map& Menu::GetDisabledMap() { + return disabledMap; +} + +void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex) { + disabledTempTooltip = "This setting is disabled because: \n\n"; + disabledValue = false; + disabledTooltip = " "; + + if (widget.preFunc != nullptr) { + widget.ResetDisables(); + widget.preFunc(widget); + if (widget.isHidden) { + return; + } + if (!widget.activeDisables.empty()) { + widget.options->disabled = true; + for (auto option : widget.activeDisables) { + disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n"); + } + widget.options->disabledTooltip = disabledTempTooltip.c_str(); + } + } + + if (widget.sameLine) { + ImGui::SameLine(); + } + + try { + switch (widget.type) { + case WIDGET_CHECKBOX: { + bool* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Checkbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), pointer, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_CHECKBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCheckbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), widget.cVar, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_AUDIO_BACKEND: { + auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend(); + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the audio API used by the game. Requires a relaunch to take effect."; + options.disabled = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1; + options.disabledTooltip = "Only one audio API is available on this platform."; + if (UIWidgets::Combobox("Audio API", ¤tAudioBackend, audioBackendsMap, options)) { + Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(currentAudioBackend); + } + } break; + case WIDGET_VIDEO_BACKEND: { + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the renderer API used by the game."; + options.disabled = availableWindowBackends->size() <= 1; + options.disabledTooltip = "Only one renderer API is available on this platform."; + if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap, + options)) { + Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", + (int32_t)(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name", + windowBackendsMap.at(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->Save(); + UpdateWindowBackendObjects(); + } + } break; + case WIDGET_SEPARATOR: { + ImGui::Separator(); + } break; + case WIDGET_SEPARATOR_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::SeparatorText(widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_COMBOBOX: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Combobox(widget.name.c_str(), pointer, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_COMBOBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCombobox(widget.name.c_str(), widget.cVar, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_SLIDER_INT: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderInt(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_SLIDER_INT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderInt(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_SLIDER_FLOAT: { + float* pointer = std::get(widget.valuePointer); + + if (pointer == nullptr) { + SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderFloat(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_SLIDER_FLOAT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderFloat(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_BUTTON: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Button(widget.name.c_str(), *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CUSTOM: { + if (widget.customFunction != nullptr) { + widget.customFunction(widget); + } + } break; + case WIDGET_WINDOW_BUTTON: { + if (widget.windowName == nullptr || widget.windowName[0] == '\0') { + std::string msg = + fmt::format("Error drawing window contents for {}: windowName not defined", widget.name); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.windowName); + if (!window) { + std::string msg = + fmt::format("Error drawing window contents: windowName {} does not exist", widget.windowName); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (options->showButton) { + UIWidgets::WindowButton(widget.name.c_str(), widget.cVar, window, *options); + } + if (!window->IsVisible() && options->embedWindow) { + window->DrawElement(); + } + } break; + case WIDGET_SEARCH: { + UIWidgets::PushStyleButton(menuThemeIndex); + if (ImGui::Button("Clear")) { + menuSearch.Clear(); + } + ImGui::SameLine(); + if (CVarGetInteger(CVAR_SETTING("Menu.SearchAutofocus"), 0) && + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + UIWidgets::PushStyleCombobox(menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(menuThemeIndex)); + menuSearch.Draw(); + ImGui::PopStyleColor(); + UIWidgets::PopStyleCombobox(); + UIWidgets::PopStyleButton(); + std::string menuSearchText(menuSearch.InputBuf); + + if (menuSearchText == "") { + ImGui::Text("Start typing to see results."); + return; + } + DrawSearchResults(menuSearchText); + ImGui::EndChild(); + } break; + default: + break; + } + if (widget.postFunc != nullptr) { + widget.postFunc(widget); + } + } catch (const std::bad_variant_access& e) { + SPDLOG_ERROR("Failed to draw menu item \"{}\" due to: {}", widget.name, e.what()); + assert(false); + } +} + +void Menu::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void Menu::DrawElement() { + for (auto& [reason, info] : disabledMap) { + info.active = info.evaluation(info); + } + + windowHeight = ImGui::GetMainViewport()->WorkSize.y; + windowWidth = ImGui::GetMainViewport()->WorkSize.x; + auto windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + bool popout = CVarGetInteger(CVAR_SETTING("Menu.Popout"), 0) && allowPopout; + if (popout) { + windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking; + } + if (popout != popped) { + if (popout) { + windowHeight = poppedSize.y; + windowWidth = poppedSize.x; + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, + ImGuiCond_Always); + ImGui::SetNextWindowPos(poppedPos, ImGuiCond_Always); + } else if (popped) { + CVarSetFloat(CVAR_SETTING("Menu.PoppedWidth"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedHeight"), poppedSize.y); + CVarSave(); + } + } + popped = popout; + auto windowCond = ImGuiCond_Always; + if (!popout) { + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, windowCond); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), windowCond, { 0.5f, 0.5f }); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + } + if (!ImGui::Begin("Main Menu", NULL, windowFlags)) { + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::End(); + return; + } + if (popped != popout) { + if (!popout) { + ImGui::PopStyleVar(); + } + CVarSetInteger(CVAR_SETTING("Menu.Popout"), popped); + CVarSetFloat(CVAR_SETTING("Menu.PoppedWidth"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedHeight"), poppedSize.y); + CVarSetFloat(CVAR_SETTING("Menu.PoppedPos.x"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedPos.y"), poppedSize.y); + CVarSave(); + ImGui::End(); + return; + } + ImGui::PushFont(OTRGlobals::Instance->fontStandardLargest); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + windowHeight = window->WorkRect.GetHeight(); + windowWidth = window->WorkRect.GetWidth(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + const char* headerCvar = CVAR_SETTING("Menu.ActiveHeader"); + std::string headerIndex = CVarGetString(headerCvar, "Settings"); + ImVec2 pos = window->DC.CursorPos; + float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1)); + std::vector headerSizes; + float headerWidth = style.ItemSpacing.x + 20; + bool headerSearch = !CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0); + if (headerSearch) { + headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x; + } + for (auto& label : menuOrder) { + ImVec2 size = ImGui::CalcTextSize(label.c_str()); + headerSizes.push_back(size); + headerWidth += size.x + style.FramePadding.x * 2; + if (label == headerIndex) { + headerWidth += style.ItemSpacing.x; + } + } + + // Full screen menu with widths below 1280, heights below 800. + // Up to 100 pixel padding when up to 1700 width, 1050 height. + // Everything above that, fixed size of 1600x950. + ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; + if (windowWidth > 1380) { + menuSize.x = std::fminf(1600, windowWidth - 100); + } + if (windowHeight > 900) { + menuSize.y = std::fminf(950, windowHeight - 100); + } + + pos += window->WorkRect.GetSize() / 2 - menuSize / 2; + ImGui::SetNextWindowPos(pos); + ImGui::BeginChild("Menu Block", menuSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar); + + std::unordered_map* sidebar; + float headerHeight = headerSizes.at(0).y + style.FramePadding.y * 2; + ImVec2 buttonSize = ImGui::CalcTextSize(ICON_FA_TIMES_CIRCLE) + style.FramePadding * 2; + bool scrollbar = false; + if (headerWidth > menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3) { + headerHeight += style.ScrollbarSize; + scrollbar = true; + } + UIWidgets::ButtonOptions options = {}; + options.size = UIWidgets::Sizes::Inline; + options.tooltip = "Close Menu (Esc)"; + if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) { + ToggleVisibility(); + + // Update gamepad navigation after close based on if other menus are still visible + auto mImGuiIo = &ImGui::GetIO(); + if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) && + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) { + mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + } else { + mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; + } + } + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight }); + ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight }; + if (scrollbar) { + headerSelSize.y += style.ScrollbarSize; + } + bool autoFocus = CVarGetInteger(CVAR_SETTING("Menu.SearchAutofocus"), 0); + ImGui::BeginChild("Header Selection", headerSelSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar); + uint8_t curIndex = 0; + for (auto& label : menuOrder) { + if (curIndex != 0) { + ImGui::SameLine(); + } + auto& entry = menuEntries.at(label); + std::string nextIndex = label; + UIWidgets::PushStyleButton(menuThemeIndex); + if (headerIndex != label) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuHeaderEntry(entry.label)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(headerCvar, label.c_str()); + CVarSave(); + nextIndex = label; + } + if (headerIndex != label) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (headerIndex == label) { + sidebar = &entry.sidebars; + } + if (nextIndex != label) { + headerIndex = nextIndex; + } + curIndex++; + } + std::string menuSearchText = ""; + if (headerSearch) { + ImGui::SameLine(); + if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + auto color = UIWidgets::ColorValues.at(menuThemeIndex); + color.w = 0.2f; + ImGui::PushStyleColor(ImGuiCol_FrameBg, color); + menuSearch.Draw("##search", 200.0f); + menuSearchText = menuSearch.InputBuf; + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), menuSearchText.end()); + if (menuSearchText.length() < 1) { + ImGui::SameLine(headerWidth - 200.0f + style.ItemSpacing.x); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search..."); + } + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x); + UIWidgets::ButtonOptions options2 = {}; + options2.color = UIWidgets::Colors::Red; + options2.size = UIWidgets::Sizes::Inline; + options2.tooltip = "Reset" +#ifdef __APPLE__ + " (Command-R)" +#elif !defined(__SWITCH__) && !defined(__WIIU__) + " (Ctrl+R)" +#else + "" +#endif + ; + if (UIWidgets::Button(ICON_FA_UNDO, options2)) { + std::reinterpret_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")) + ->Dispatch("reset"); + } + ImGui::SameLine(); + UIWidgets::ButtonOptions options3 = {}; + options3.color = UIWidgets::Colors::Red; + options3.size = UIWidgets::Sizes::Inline; + options3.tooltip = "Quit SoH"; + if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { + if (!popped) { + ToggleVisibility(); + } + Ship::Context::GetInstance()->GetWindow()->Close(); + } + ImGui::PopStyleVar(); + + pos.y += headerHeight + style.ItemSpacing.y; + pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1)); + window->DrawList->AddRectFilled(pos, pos + ImVec2{ menuSize.x, 4 }, ImGui::GetColorU32({ 255, 255, 255, 255 }), + true, style.WindowRounding); + pos.y += style.ItemSpacing.y; + float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2; + float columnHeight = sectionHeight - style.ItemSpacing.y * 4; + ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2); + float sidebarWidth = 200 - style.ItemSpacing.x; + + const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar; + + std::string sectionIndex = CVarGetString(sidebarCvar, ""); + if (!sidebar->contains(sectionIndex)) { + sectionIndex = sidebar->begin()->first; + } + float sectionCenterX = pos.x + (sidebarWidth / 2); + float topY = pos.y; + ImGui::SetNextWindowSizeConstraints({ sidebarWidth, 0 }, { sidebarWidth, columnHeight }); + ImGui::BeginChild((menuEntries.at(headerIndex).label + " Section").c_str(), { sidebarWidth, columnHeight * 3 }, + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoTitleBar); + for (auto& sidebarLabel : menuEntries.at(headerIndex).sidebarOrder) { + std::string nextIndex = ""; + UIWidgets::PushStyleButton(menuThemeIndex); + if (sectionIndex != sidebarLabel) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuSidebarEntry(sidebarLabel)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(sidebarCvar, sidebarLabel.c_str()); + CVarSave(); + nextIndex = sidebarLabel; + } + if (sectionIndex != sidebarLabel) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (nextIndex != "") { + sectionIndex = nextIndex; + } + } + ImGui::EndChild(); + + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2; + window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 }, + ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding); + pos.x += 4 + style.ItemSpacing.x; + ImGui::SetNextWindowPos(pos + style.ItemSpacing); + float sectionWidth = menuSize.x - sidebarWidth - 4 - style.ItemSpacing.x * 4; + std::string sectionMenuId = sectionIndex + " Settings"; + int columns = sidebar->at(sectionIndex).columnCount; + size_t columnFuncs = sidebar->at(sectionIndex).columnWidgets.size(); + if (windowWidth < 800) { + columns = 1; + } + float columnWidth = (sectionWidth - style.ItemSpacing.x * columns) / columns; + bool useColumns = columns > 1; + if (!useColumns || (headerSearch && menuSearchText.length() > 0)) { + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ sectionWidth, 0 }, { sectionWidth, columnHeight }); + ImGui::BeginChild(sectionMenuId.c_str(), { sectionWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + if (headerSearch && menuSearchText.length() > 0) { + uint32_t searchCount = DrawSearchResults(menuSearchText); + if (searchCount == 0) { + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No results found").x) / 2); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "No results found"); + } + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Clear Search").x) / 2 - 10.0f); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + UIWidgets::ButtonOptions clearBtnOpts = {}; + clearBtnOpts.size = UIWidgets::Sizes::Inline; + if (UIWidgets::Button("Clear Search", clearBtnOpts)) { + menuSearch.Clear(); + } + + ImGui::EndChild(); + } else { + std::string menuLabel = menuEntries.at(headerIndex).label; + if (MenuInit::GetUpdateFuncs().contains(menuLabel)) { + if (MenuInit::GetUpdateFuncs()[menuLabel].contains(sectionIndex)) { + for (auto& updateFunc : MenuInit::GetUpdateFuncs()[menuLabel][sectionIndex]) { + updateFunc(); + } + } + } + for (int i = 0; i < columnFuncs; i++) { + std::string sectionId = fmt::format("{} Column {}", sectionMenuId, i); + if (useColumns) { + ImGui::SetNextWindowSizeConstraints({ columnWidth, 0 }, { columnWidth, columnHeight }); + ImGui::BeginChild(sectionId.c_str(), { columnWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + // for (auto& entryName : sidebar->at(sectionIndex).sidebarOrder) { + for (auto& entry : sidebar->at(sectionIndex).columnWidgets.at(i)) { + MenuDrawItem(entry, 90 / sidebar->at(sectionIndex).columnCount, menuThemeIndex); + } + //} + if (useColumns) { + ImGui::EndChild(); + } + if (i < columns - 1) { + ImGui::SameLine(); + } + } + } + if (!useColumns || menuSearchText.length() > 0) { + ImGui::EndChild(); + } + ImGui::PopFont(); + ImGui::PopFont(); + + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + if (popout) { + poppedSize = ImGui::GetWindowSize(); + poppedPos = ImGui::GetWindowPos(); + } + ImGui::End(); +} +} // namespace Ship diff --git a/soh/soh/SohGui/Menu.h b/soh/soh/SohGui/Menu.h new file mode 100644 index 000000000..d7007bb1e --- /dev/null +++ b/soh/soh/SohGui/Menu.h @@ -0,0 +1,65 @@ +#ifndef MENU_H +#define MENU_H + +#include +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "MenuTypes.h" + +namespace Ship { +uint32_t GetVectorIndexOf(std::vector& vector, std::string value); +class Menu : public GuiWindow { + public: + using GuiWindow::GuiWindow; + + Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_ = 0, + UIWidgets::Colors menuThemeIndex_ = UIWidgets::Colors::LightBlue); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + void InsertSidebarSearch(); + void RemoveSidebarSearch(); + void UpdateWindowBackendObjects(); + UIWidgets::Colors GetMenuThemeColor(); + + void MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex); + void AddMenuEntry(std::string entryName, const char* entryCvar); + std::unordered_map& GetDisabledMap(); + + protected: + ImVec2 mOriginalSize; + std::string mName; + uint32_t mWindowFlags; + std::unordered_map menuEntries; + std::vector menuOrder; + uint32_t DrawSearchResults(std::string& menuSearchText); + ImGuiTextFilter menuSearch; + uint8_t searchSidebarIndex; + UIWidgets::Colors defaultThemeIndex; + std::shared_ptr> availableWindowBackends; + std::unordered_map availableWindowBackendsMap; + Ship::WindowBackend configWindowBackend; + + std::unordered_map disabledMap; + std::vector disabledVector; + const SidebarEntry searchSidebarEntry = { + .columnCount = 1, + .columnWidgets = { { { .name = "Sidebar Search", + .type = WIDGET_SEARCH, + .options = std::make_shared(UIWidgets::WidgetOptions{}.Tooltip( + "Searches all menus for the given text, including tooltips.")) } } } + }; + + private: + bool allowPopout = true; // PortNote: should be set to false on small screen ports + bool popped; + ImVec2 poppedSize; + ImVec2 poppedPos; + float windowHeight; + float windowWidth; + UIWidgets::Colors menuThemeIndex; +}; +} // namespace Ship + +#endif // MENU_H diff --git a/soh/soh/SohGui/MenuTypes.h b/soh/soh/SohGui/MenuTypes.h new file mode 100644 index 000000000..7b979a824 --- /dev/null +++ b/soh/soh/SohGui/MenuTypes.h @@ -0,0 +1,280 @@ +#ifndef MENUTYPES_H +#define MENUTYPES_H + +#include +#include "UIWidgets.hpp" + +typedef enum { + DISABLE_FOR_NO_VSYNC, + DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + DISABLE_FOR_NO_MULTI_VIEWPORT, + DISABLE_FOR_NOT_DIRECTX, + DISABLE_FOR_DIRECTX, + DISABLE_FOR_MATCH_REFRESH_RATE_ON, + DISABLE_FOR_ADVANCED_RESOLUTION_ON, + DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + DISABLE_FOR_LOW_RES_MODE_ON, + DISABLE_FOR_NULL_PLAY_STATE, + DISABLE_FOR_DEBUG_MODE_OFF, + DISABLE_FOR_FRAME_ADVANCE_OFF, + DISABLE_FOR_ADVANCED_RESOLUTION_OFF, + DISABLE_FOR_VERTICAL_RESOLUTION_OFF, +} DisableOption; + +struct WidgetInfo; +struct disabledInfo; +using VoidFunc = void (*)(); +using DisableInfoFunc = bool (*)(disabledInfo&); +using DisableVec = std::vector; +using WidgetFunc = void (*)(WidgetInfo&); + +typedef enum { + WIDGET_CHECKBOX, + WIDGET_COMBOBOX, + WIDGET_SLIDER_INT, + WIDGET_SLIDER_FLOAT, + WIDGET_CVAR_CHECKBOX, + WIDGET_CVAR_COMBOBOX, + WIDGET_CVAR_SLIDER_INT, + WIDGET_CVAR_SLIDER_FLOAT, + WIDGET_BUTTON, + WIDGET_INPUT, + WIDGET_CVAR_INPUT, + WIDGET_COLOR_24, // color picker without alpha + WIDGET_COLOR_32, // color picker with alpha + WIDGET_SEARCH, + WIDGET_SEPARATOR, + WIDGET_SEPARATOR_TEXT, + WIDGET_TEXT, + WIDGET_WINDOW_BUTTON, + WIDGET_AUDIO_BACKEND, // needed for special operations that can't be handled easily with the normal combobox widget + WIDGET_VIDEO_BACKEND, // same as above + WIDGET_CUSTOM, +} WidgetType; + +typedef enum { + SECTION_COLUMN_1, + SECTION_COLUMN_2, + SECTION_COLUMN_3, +} SectionColumns; + +typedef enum { + DEBUG_LOG_TRACE, + DEBUG_LOG_DEBUG, + DEBUG_LOG_INFO, + DEBUG_LOG_WARN, + DEBUG_LOG_ERROR, + DEBUG_LOG_CRITICAL, + DEBUG_LOG_OFF, +} DebugLogOption; + +// holds the widget values for a widget, contains all CVar types available from LUS. int32_t is used for boolean +// evaluation +using CVarVariant = std::variant; +using OptionsVariant = + std::variant; + +// All the info needed for display and search of all widgets in the menu. +// `name` is the label displayed, +// `cVar` is the string representation of the CVar used to store the widget value +// `tooltip` is what is displayed when hovering (except when disabled, more on that later) +// `type` is the WidgetType for the widget, which is what determines how the information is used in the draw func +// `options` is a variant that holds the UIWidgetsOptions struct for the widget type +// blank objects need to be initialized with specific typing matching the expected Options struct for the widget +// `callback` is a lambda used for running code on widget change. may need `SohGui::GetMenu()` for specific menu actions +// `preFunc` is a lambda called before drawing code starts. It can be used to determine a widget's status, +// whether disabled or hidden, as well as update pointers for non-CVar widget types. +// `postFunc` is a lambda called after all drawing code is finished, for reacting to states other than +// widgets having been changed, like holding buttons. +// All three lambdas accept a `widgetInfo` reference in case it needs information on the widget for these operations +// `activeDisables` is a vector of DisableOptions for specifying what reasons a widget is disabled, which are displayed +// in the disabledTooltip for the widget. Can display multiple reasons. Handling the reasons is done in `preFunc`. +// It is recommended to utilize `disabledInfo`/`DisableReason` to list out all reasons for disabling and isHidden so +// the info can be shown. +// `windowName` is what is displayed and searched for `windowButton` type and window interactions +// `isHidden` just prevents the widget from being drawn under whatever circumstances you specify in the `preFunc` +// `sameLine` allows for specifying that the widget should be on the same line as the previous widget +struct WidgetInfo { + std::string name; // Used by all widgets + const char* cVar; // Used by all widgets except + WidgetType type; + std::shared_ptr options; + std::variant valuePointer; + WidgetFunc callback = nullptr; + WidgetFunc preFunc = nullptr; + WidgetFunc postFunc = nullptr; + WidgetFunc customFunction = nullptr; + DisableVec activeDisables = {}; + const char* windowName = ""; + bool isHidden = false; + bool sameLine = false; + + WidgetInfo& CVar(const char* cVar_) { + cVar = cVar_; + return *this; + } + WidgetInfo& Options(OptionsVariant options_) { + switch (type) { + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_BUTTON: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_WINDOW_BUTTON: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_TEXT: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_SEPARATOR: + default: + options = std::make_shared(std::get(options_)); + } + return *this; + } + void ResetDisables() { + isHidden = false; + options->disabled = false; + options->disabledTooltip = ""; + activeDisables.clear(); + } + WidgetInfo& Options(std::shared_ptr options_) { + options = options_; + return *this; + } + WidgetInfo& Callback(WidgetFunc callback_) { + callback = callback_; + return *this; + } + WidgetInfo& PreFunc(WidgetFunc preFunc_) { + preFunc = preFunc_; + return *this; + } + WidgetInfo& PostFunc(WidgetFunc postFunc_) { + postFunc = postFunc_; + return *this; + } + WidgetInfo& WindowName(const char* windowName_) { + windowName = windowName_; + return *this; + } + WidgetInfo& ValuePointer(std::variant valuePointer_) { + valuePointer = valuePointer_; + return *this; + } + WidgetInfo& SameLine(bool sameLine_) { + sameLine = sameLine_; + return *this; + } + WidgetInfo& CustomFunction(WidgetFunc customFunction_) { + customFunction = customFunction_; + return *this; + } +}; + +struct WidgetPath { + std::string sectionName; + std::string sidebarName; + SectionColumns column; +}; + +// `disabledInfo` holds information on reasons for hiding or disabling a widget, as well as an evaluation lambda that +// is run once per frame to update its status (this is done to prevent dozens of redundant CVarGets in each frame loop) +// `evaluation` returns a bool which can be determined by whatever code you want that changes its status +// `reason` is the text displayed in the disabledTooltip when a widget is disabled by a particular DisableReason +// `active` is what's referenced when determining disabled status for a widget that uses this This can also be used to +// hold reasons to hide widgets so that their evaluations are also only run once per frame +struct disabledInfo { + DisableInfoFunc evaluation; + const char* reason; + bool active = false; + int32_t value = 0; +}; + +// Contains the name displayed in the sidebar (label), the number of columns to use in drawing (columnCount; for visual +// separation, 1-3), and nested vectors of the widgets, grouped by column (columnWidgets). The number of widget vectors +// added to the column groups does not need to match the specified columnCount, e.g. you can have one vector added to +// the sidebar, but still separate the window into 3 columns and display only in the first column +struct SidebarEntry { + uint32_t columnCount; + std::vector> columnWidgets; +}; + +// Contains entries for what's listed in the header at the top, including the name displayed on the top bar (label), +// a vector of the SidebarEntries for that header entry, and the name of the cvar used to track what sidebar entry is +// the last viewed for that header. +struct MainMenuEntry { + std::string label; + const char* sidebarCvar; + std::unordered_map sidebars = {}; + std::vector sidebarOrder = {}; +}; + +static const std::unordered_map audioBackendsMap = { + { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, + { Ship::AudioBackend::SDL, "SDL" }, +}; + +static const std::unordered_map windowBackendsMap = { + { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" }, + { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" }, + { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" }, +}; + +struct MenuInit { + static std::vector>& GetInitFuncs() { + static std::vector> menuInitFuncs; + return menuInitFuncs; + } + + static std::unordered_map>>>& + GetUpdateFuncs() { + static std::unordered_map>>> + menuUpdateFuncs; + return menuUpdateFuncs; + } + + static void InitAll() { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + for (const auto& initFunc : menuInitFuncs) { + initFunc(); + } + } +}; + +struct RegisterMenuInitFunc { + RegisterMenuInitFunc(std::function initFunc) { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + + menuInitFuncs.push_back(initFunc); + } +}; + +struct RegisterMenuUpdateFunc { + RegisterMenuUpdateFunc(std::function updateFunc, std::string sectionName, std::string sidebarName) { + auto& menuUpdateFuncs = MenuInit::GetUpdateFuncs(); + + menuUpdateFuncs[sectionName][sidebarName].push_back(updateFunc); + } +}; + +#endif // MENUTYPES_H diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp new file mode 100644 index 000000000..8e850682c --- /dev/null +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -0,0 +1,528 @@ +#include "ResolutionEditor.h" +#include +#include + +#include "soh/SohGui/UIWidgets.hpp" +#include +#include "soh/OTRGlobals.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" + +/* Console Variables are grouped under gAdvancedResolution. (e.g. CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled") + + The following cvars are used in Libultraship and can be edited here: + - Enabled - Turns Advanced Resolution Mode on. + - AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero. + - VerticalPixelCount, VerticalResolutionToggle - Resolution controls. + - PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls. + - IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode. + - IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds. + + The following cvars are also implemented in LUS for niche use cases: + - IgnoreAspectCorrection - Stretch framebuffer to fill screen. + This is something of a power-user setting for niche setups that most people won't need or care about, + but may be useful if playing the Switch/Wii U ports on a 4:3 television. + - IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1. + This isn't that useful at the moment, so it's unused here. +*/ + +namespace SohGui { +extern std::shared_ptr mSohMenu; +enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount }; + +std::unordered_map aspectRatioPresetLabels = { { 0, "Off" }, + { 1, "Custom" }, + { 2, "Original (4:3)" }, + { 3, "Widescreen (16:9)" }, + { 4, "Nintendo 3DS (5:3)" }, + { 5, "16:10 (8:5)" }, + { 6, "Ultrawide (21:9)" } }; +const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f }; +const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f }; +const int default_aspectRatio = 1; // Default combo list option + +const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)", + "5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" }; +const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 }; +const int default_pixelCount = 0; // Default combo list option + +// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges() +const uint32_t minVerticalPixelCount = SCREEN_HEIGHT; +const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution + +const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider. + +enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 }; +const ImVec4 messageColor[]{ + { 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR + { 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING + { 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION + { 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO + { 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75 +}; +static const float enhancementSpacerHeight = 19.0f; +// Initialise update flags. +static bool update[3]; + +// Initialise integer scale bounds. +static short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get + // overridden depending on viewport res + +static short integerScale_maximumBounds = 1; // can change when window is resized + +// Combo List defaults +static int32_t item_aspectRatio; +static int32_t item_pixelCount; +// Stored Values for non-UIWidgets elements +static float aspectRatioX; +static float aspectRatioY; +static int32_t verticalPixelCount; +// Additional settings +static bool showHorizontalResField; +static int32_t horizontalPixelCount; +// Disabling flags +static bool disabled_everything; +static bool disabled_pixelCount; + +using namespace UIWidgets; + +void ResolutionCustomWidget(WidgetInfo& info) { + ImGui::BeginDisabled(disabled_everything); + // Vertical Resolution + UIWidgets::CVarCheckbox("Set fixed vertical resolution (disables Resolution slider)", CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", + UIWidgets::CheckboxOptions({ {.disabled = disabled_everything} }).Tooltip("Override the resolution scale slider and use the settings below, irrespective of window size.") + .Color(THEME_COLOR)); + //if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. + // UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + //} + UIWidgets::PushStyleCombobox(THEME_COLOR); + if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels, + IM_ARRAYSIZE(pixelCountPresetLabels)) && + item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected. + verticalPixelCount = pixelCountPresets[item_pixelCount]; + + if (showHorizontalResField) { + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + UIWidgets::PopStyleCombobox(); + // Horizontal Resolution, if visibility is enabled for it. + if (showHorizontalResField) { + // Only show the field if Aspect Ratio is being enforced. + if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) { + // So basically we're "faking" this one by setting aspectRatioX instead. + UIWidgets::PushStyleInput(THEME_COLOR); + if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) { + item_aspectRatio = default_aspectRatio; + if (horizontalPixelCount < SCREEN_WIDTH) { + horizontalPixelCount = SCREEN_WIDTH; + } + aspectRatioX = horizontalPixelCount; + aspectRatioY = verticalPixelCount; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + UIWidgets::PopStyleInput(); + } else { // Display a notice instead. + ImGui::TextColored(messageColor[MESSAGE_QUESTION], + ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required."); + // ImGui::Text(" "); + ImGui::SameLine(); + if (UIWidgets::Button("Click to resolve", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + item_aspectRatio = default_aspectRatio; // Set it to Custom + aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults + aspectRatioY = aspectRatioPresetsY[2]; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + } + } + // Vertical Resolution part 2 + UIWidgets::PushStyleInput(THEME_COLOR); + if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) { + item_pixelCount = default_pixelCount; + update[UPDATE_verticalPixelCount] = true; + + // Account for the natural instinct to enter horizontal first. + // Ignore vertical resolutions that are below the lower clamp constant. + if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) { + item_aspectRatio = default_aspectRatio; + aspectRatioX = horizontalPixelCount; + aspectRatioY = verticalPixelCount; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + } + ImGui::EndDisabled(); + UIWidgets::PopStyleInput(); + + // Integer scaling settings group (Pixel-perfect Mode) + static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag = + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen + : ImGuiTreeNodeFlags_None; + UIWidgets::PushStyleHeader(THEME_COLOR); + if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) { + const bool disabled_pixelPerfectMode = + !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything; + // Pixel-perfect Mode + UIWidgets::CVarCheckbox("Pixel-perfect Mode", CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", + UIWidgets::CheckboxOptions({{ .disabled = disabled_pixelCount || disabled_everything }}).Tooltip("Don't scale image to fill window.") + .Color(THEME_COLOR)); + if (disabled_pixelCount && CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0)) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Integer Scaling + UIWidgets::CVarSliderInt(fmt::format("Integer scale factor: {}", max_integerScaleFactor).c_str(), CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", + UIWidgets::IntSliderOptions({ {.disabled = disabled_pixelPerfectMode || CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)} }) + .Min(1).Max(max_integerScaleFactor).DefaultValue(1).Tooltip("Integer scales the image. Only available in pixel-perfect mode.").Color(THEME_COLOR)); + // Display warning if size is being clamped or if framebuffer is larger than viewport. + if (!disabled_pixelPerfectMode && + (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) && + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1) > + integerScale_maximumBounds)) { + ImGui::SameLine(); + ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded."); + } + + UIWidgets::CVarCheckbox("Automatically scale image to fit viewport", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", + UIWidgets::CheckboxOptions({ {.disabled = disabled_pixelPerfectMode} }).DefaultValue(true).Color(THEME_COLOR) + .Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode.")); + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)) { + // This is just here to update the value shown on the slider. + // The function in LUS to handle this setting will ignore IntegerScaleFactor while active. + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", integerScale_maximumBounds); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } // End of integer scaling settings + UIWidgets::PopStyleHeader(); + + // Collapsible panel for additional settings + UIWidgets::PushStyleHeader(THEME_COLOR); + if (ImGui::CollapsingHeader("Additional Settings")) { + #if defined(__SWITCH__) || defined(__WIIU__) + // Disable aspect correction, stretching the framebuffer to fill the viewport. + // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles. + // The associated cvar is still functional on PC platforms if you want to use it though. + UIWidgets::CVarCheckbox("Disable aspect correction and stretch the output image.\n" + "(Might be useful for 4:3 televisions!)\n" + "Not available in Pixel Perfect Mode.", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", + UIWidgets::CheckboxOptions({{ .disabled = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything}}) + .Color(THEME_COLOR)); + #else + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0)) { + // This setting is intentionally not exposed on PC platforms, + // but may be accidentally activated for varying reasons. + // Having this button should hopefully prevent support headaches. + ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE + " If the image is stretched and you don't know why, click this."); + if (ImGui::Button("Click to reenable aspect correction.")) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + UIWidgets::Spacer(2); + } + #endif + + // A requested addition; an alternative way of displaying the resolution field. + if (UIWidgets::Checkbox("Show a horizontal resolution field, instead of aspect ratio.", + &showHorizontalResField, UIWidgets::CheckboxOptions().Color(THEME_COLOR))) { + if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off + // Refresh relevant values + aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } else { // when turning this setting on + item_aspectRatio = default_aspectRatio; + if (aspectRatioX > 0.0f) { + // Refresh relevant values in the opposite order + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; + } + } + update[UPDATE_aspectRatioX] = true; + } + + // Beginning of Integer Scaling additional settings. + { + // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); + + // Integer Scaling - Never Exceed Bounds. + const bool disabled_neverExceedBounds = + !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0) || + disabled_everything; + if (UIWidgets::CVarCheckbox("Prevent integer scaling from exceeding screen bounds.\n" + "(Makes screen bounds take priority over specified factor.)", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", UIWidgets::CheckboxOptions({{ .disabled = disabled_neverExceedBounds}}) + .Tooltip( + "Prevents integer scaling factor from exceeding screen bounds.\n\n" + "Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n" + "Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n" + " " ICON_FA_INFO_CIRCLE + " Please note that exceeding screen bounds may show a scroll bar on-screen.").Color(THEME_COLOR).DefaultValue(true))) { + + // Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed. + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + // Integer Scaling - Exceed Bounds By 1x/Offset. + // A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p 5x". + UIWidgets::CVarCheckbox("Allow integer scale factor to go +1 above maximum screen bounds.", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", + UIWidgets::CheckboxOptions({{ .disabled = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything }}).Color(THEME_COLOR)); + + // It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar. + // I've ended up commenting this one out because of the scroll bar, and for simplicity. + + // Display an info message about the scroll bar. + if (!CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) || + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + ImGui::TextColored(messageColor[MESSAGE_INFO], + " " ICON_FA_INFO_CIRCLE + " A scroll bar may become visible if screen bounds are exceeded."); + + // Another support helper button, to disable the unused "Exceed Bounds By" cvar. + // (Remove this button if uncommenting the checkbox.) + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + if (UIWidgets::Button("Click to reset a console variable that may be causing this.", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + } else { + ImGui::Text(" "); + } + } // End of Integer Scaling additional settings. + + } // End of additional settings + UIWidgets::PopStyleHeader(); + + // Clamp and update the cvars that don't use UIWidgets + if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { + if (update[UPDATE_aspectRatioX]) { + if (aspectRatioX < 0.0f) { + aspectRatioX = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + } + if (update[UPDATE_aspectRatioY]) { + if (aspectRatioY < 0.0f) { + aspectRatioY = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + if (update[UPDATE_verticalPixelCount]) { + // There's a upper and lower clamp on the Libultraship side too, + // so clamping it here is entirely visual, so the vertical resolution field reflects it. + if (verticalPixelCount < minVerticalPixelCount) { + verticalPixelCount = minVerticalPixelCount; + } + if (verticalPixelCount > maxVerticalPixelCount) { + verticalPixelCount = maxVerticalPixelCount; + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } +} + +void RegisterResolutionWidgets() { + WidgetPath path = { "Settings", "Graphics", SECTION_COLUMN_2 }; + + // Resolution visualiser + mSohMenu->AddWidget(path, "Viewport dimensions: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + info.name = fmt::format("Viewport dimensions: {} x {}", gfx_current_game_window_viewport.width, + gfx_current_game_window_viewport.height); + }); + mSohMenu->AddWidget(path, "Internal resolution: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + info.name = + fmt::format("Internal resolution: {} x {}", gfx_current_dimensions.width, gfx_current_dimensions.height); + }); + + // Activator + mSohMenu->AddWidget(path, "Enable advanced settings.", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled"); + // Error/Warning display + mSohMenu + ->AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.", + WIDGET_TEXT) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = !(!CVarGetInteger(CVAR_LOW_RES_MODE, 0) && IsDroppingFrames()); }) + .Options(WidgetOptions().Color(Colors::Orange)); + mSohMenu->AddWidget(path, ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) + .Options(WidgetOptions().Color(Colors::LightBlue)); + mSohMenu->AddWidget(path, "Click to disable N64 mode", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_LOW_RES_MODE, 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + // Aspect Ratio + mSohMenu->AddWidget(path, "AspectSep", WIDGET_SEPARATOR) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }); + mSohMenu->AddWidget(path, "Force aspect ratio:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }); + mSohMenu->AddWidget(path, "(Select \"Off\" to disable.)", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }) + .SameLine(true) + .Options(WidgetOptions().Color(Colors::Gray)); + // Presets + mSohMenu->AddWidget(path, "Aspect Ratio", WIDGET_COMBOBOX) + .ValuePointer(&item_aspectRatio) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }) + .Callback([](WidgetInfo& info) { + if (item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected. + aspectRatioX = aspectRatioPresetsX[item_aspectRatio]; + aspectRatioY = aspectRatioPresetsY[item_aspectRatio]; + + if (showHorizontalResField) { + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(ComboboxOptions().ComboMap(aspectRatioPresetLabels)); + mSohMenu->AddWidget(path, "AspectRatioCustom", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { + // Hide aspect ratio input fields if using one of the presets. + if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) { + // Declare input interaction bools outside of IF statement to prevent Y field from disappearing. + const bool input_X = UIWidgets::SliderFloat("X", &aspectRatioX, + UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(32.0f).Step(0.001f).Format("%3f") + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + const bool input_Y = UIWidgets::SliderFloat("Y", &aspectRatioY, + UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(24.0f).Step(0.001f).Format("%3f") + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + if (input_X || input_Y) { + item_aspectRatio = default_aspectRatio; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + } else if (showHorizontalResField) { // Show calculated aspect ratio + if (item_aspectRatio) { + ImGui::Dummy({ 0, 2 }); + const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height; + ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio); + } + } + }); + mSohMenu->AddWidget(path, "MoreResolutionSettings", WIDGET_CUSTOM).CustomFunction(ResolutionCustomWidget); +} + +void UpdateResolutionVars() { + // Clamp and update the cvars that don't use UIWidgets + if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { + if (update[UPDATE_aspectRatioX]) { + if (aspectRatioX < 0.0f) { + aspectRatioX = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + } + if (update[UPDATE_aspectRatioY]) { + if (aspectRatioY < 0.0f) { + aspectRatioY = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + if (update[UPDATE_verticalPixelCount]) { + // There's a upper and lower clamp on the Libultraship side too, + // so clamping it here is entirely visual, so the vertical resolution field reflects it. + if (verticalPixelCount < minVerticalPixelCount) { + verticalPixelCount = minVerticalPixelCount; + } + if (verticalPixelCount > maxVerticalPixelCount) { + verticalPixelCount = maxVerticalPixelCount; + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + // Initialise update flags. + for (uint8_t i = 0; i < sizeof(update); i++) { + update[i] = false; + } + + // Initialise integer scale bounds. + short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get + // overridden depending on viewport res + + short integerScale_maximumBounds = 1; // can change when window is resized + // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS. + if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) > + ((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) { + // Scale to window height + integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height; + } else { + // Scale to window width + integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width; + } + // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS. + // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded. + if (default_maxIntegerScaleFactor < integerScale_maximumBounds) { + max_integerScaleFactor = integerScale_maximumBounds + + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + } + + // Combo List defaults + item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3); + item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount); + // Stored Values for non-UIWidgets elements + aspectRatioX = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]); + aspectRatioY = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]); + verticalPixelCount = + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]); + // Additional settings + showHorizontalResField = false; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + // Disabling flags + disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); + disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); +} + +bool IsDroppingFrames() { + // a rather imprecise way of checking for frame drops. + // but it's mostly there to inform the player of large drops. + const short targetFPS = CVarGetInteger("gInterpolationFPS", 20); + const float threshold = targetFPS / 20.0f + 4.1f; + return ImGui::GetIO().Framerate < targetFPS - threshold; +} + + static RegisterMenuUpdateFunc updateFunc(UpdateResolutionVars, "Settings", "Graphics"); + static RegisterMenuInitFunc initFunc(RegisterResolutionWidgets); + +} // namespace BenGui diff --git a/soh/soh/SohGui/ResolutionEditor.h b/soh/soh/SohGui/ResolutionEditor.h new file mode 100644 index 000000000..20d613b3b --- /dev/null +++ b/soh/soh/SohGui/ResolutionEditor.h @@ -0,0 +1,12 @@ +#ifndef RESOLUTIONEDITOR_H +#define RESOLUTIONEDITOR_H + +#include + +namespace SohGui { +bool IsDroppingFrames(); +void RegisterResolutionWidgets(); +void UpdateResolutionVars(); +} // namespace BenGui + +#endif // RESOLUTIONEDITOR_H diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 8d7a4ba45..ef8e40e8c 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -20,8 +20,7 @@ #ifdef __SWITCH__ #include #endif - -#include "UIWidgets.hpp" +#include "SohMenu.h" #include "include/global.h" #include "include/z64audio.h" #include "soh/SaveManager.h" @@ -32,48 +31,14 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/cosmetics/authenticGfxPatches.h" -#include "soh/Enhancements/resolution-editor/ResolutionEditor.h" #include "soh/Enhancements/debugger/MessageViewer.h" #include "soh/Notification/Notification.h" #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" -bool isBetaQuestEnabled = false; - -extern "C" { - void enableBetaQuest() { isBetaQuestEnabled = true; } - void disableBetaQuest() { isBetaQuestEnabled = false; } -} - - namespace SohGui { // MARK: - Properties - - static const char* chestSizeAndTextureMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" }; static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" }; - static const char* allPowers[9] = { - "Vanilla (1x)", - "Double (2x)", - "Quadruple (4x)", - "Octuple (8x)", - "Foolish (16x)", - "Ridiculous (32x)", - "Merciless (64x)", - "Pure Torture (128x)", - "OHKO (256x)" }; - static const char* subPowers[8] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6], allPowers[7] }; - static const char* subSubPowers[7] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6] }; - static const char* zFightingOptions[3] = { "Disabled", "Consistent Vanish", "No Vanish" }; - static const char* bonkDamageValues[8] = { - "No Damage", - "0.25 Heart", - "0.5 Heart", - "1 Heart", - "2 Hearts", - "4 Hearts", - "8 Hearts", - "OHKO" - }; static const inline std::vector> audioBackends = { #ifdef _WIN32 @@ -98,7 +63,6 @@ namespace SohGui { return buttonText; } - // MARK: - Delegates std::shared_ptr mSohMenuBar; @@ -107,7 +71,8 @@ namespace SohGui { std::shared_ptr mStatsWindow; std::shared_ptr mGfxDebuggerWindow; std::shared_ptr mInputEditorWindow; - + + std::shared_ptr mSohMenu; std::shared_ptr mAudioEditorWindow; std::shared_ptr mInputViewer; std::shared_ptr mInputViewerSettings; @@ -129,25 +94,30 @@ namespace SohGui { std::shared_ptr mTimeSplitWindow; std::shared_ptr mPlandomizerWindow; std::shared_ptr mRandomizerSettingsWindow; - std::shared_ptr mAdvancedResolutionSettingsWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; std::shared_ptr mTimeDisplayWindow; - std::shared_ptr mAboutWindow; + + UIWidgets::Colors GetMenuThemeColor() { + return mSohMenu->GetMenuThemeColor(); + } void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); - mSohMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); + /*mSohMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); gui->SetMenuBar(std::reinterpret_pointer_cast(mSohMenuBar)); - if (gui->GetMenuBar() && !gui->GetMenuBar()->IsVisible()) { + if (!gui->GetMenuBar() && !CVarGetInteger("gSettings.DisableMenuShortcutNotify", 0)) { #if defined(__SWITCH__) || defined(__WIIU__) - Notification::Emit({ .message = "Press - to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu"); #else - Notification::Emit({ .message = "Press F1 to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu"); #endif - } + }*/ + + mSohMenu = std::make_shared(CVAR_WINDOW("Menu"), "Port Menu"); + gui->SetMenu(mSohMenu); mStatsWindow = gui->GetGuiWindow("Stats"); if (mStatsWindow == nullptr) { @@ -159,9 +129,9 @@ namespace SohGui { SPDLOG_ERROR("Could not find console window"); } - mGfxDebuggerWindow = gui->GetGuiWindow("GfxDebuggerWindow"); + mGfxDebuggerWindow = gui->GetGuiWindow("Gfx Debugger"); if (mGfxDebuggerWindow == nullptr) { - SPDLOG_ERROR("Could not find input GfxDebuggerWindow"); + SPDLOG_ERROR("Could not find Gfx Debugger window"); } mInputEditorWindow = gui->GetGuiWindow("Controller Configuration"); @@ -185,7 +155,7 @@ namespace SohGui { gui->AddGuiWindow(mSaveEditorWindow); mHookDebuggerWindow = std::make_shared(CVAR_WINDOW("HookDebugger"), "Hook Debugger", ImVec2(1250, 850)); gui->AddGuiWindow(mHookDebuggerWindow); - mDLViewerWindow = std::make_shared(CVAR_WINDOW("DLViewer"), "Display List Viewer", ImVec2(520, 600)); + mDLViewerWindow = std::make_shared(CVAR_WINDOW("DisplayListViewer"), "Display List Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mDLViewerWindow); mValueViewerWindow = std::make_shared(CVAR_WINDOW("ValueViewer"), "Value Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mValueViewerWindow); @@ -193,26 +163,24 @@ namespace SohGui { gui->AddGuiWindow(mMessageViewerWindow); mGameplayStatsWindow = std::make_shared(CVAR_WINDOW("GameplayStats"), "Gameplay Stats", ImVec2(480, 550)); gui->AddGuiWindow(mGameplayStatsWindow); - mCheckTrackerWindow = std::make_shared(CVAR_WINDOW("CheckTracker"), "Check Tracker"); + mCheckTrackerWindow = std::make_shared(CVAR_WINDOW("CheckTracker"), "Check Tracker", ImVec2(400, 540)); gui->AddGuiWindow(mCheckTrackerWindow); mCheckTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("CheckTrackerSettings"), "Check Tracker Settings", ImVec2(600, 375)); gui->AddGuiWindow(mCheckTrackerSettingsWindow); - mEntranceTrackerWindow = std::make_shared(CVAR_WINDOW("EntranceTracker"), "Entrance Tracker"); + mEntranceTrackerWindow = std::make_shared(CVAR_WINDOW("EntranceTracker"), "Entrance Tracker", ImVec2(500, 750)); gui->AddGuiWindow(mEntranceTrackerWindow); mEntranceTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("EntranceTrackerSettings"), "Entrance Tracker Settings", ImVec2(600, 375)); gui->AddGuiWindow(mEntranceTrackerSettingsWindow); - mItemTrackerWindow = std::make_shared(CVAR_WINDOW("ItemTracker"), "Item Tracker"); + mItemTrackerWindow = std::make_shared(CVAR_WINDOW("ItemTracker"), "Item Tracker", ImVec2(350, 600)); gui->AddGuiWindow(mItemTrackerWindow); mItemTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("ItemTrackerSettings"), "Item Tracker Settings", ImVec2(733, 472)); gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600)); gui->AddGuiWindow(mRandomizerSettingsWindow); - mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits", ImVec2(450, 660)); + mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660)); gui->AddGuiWindow(mTimeSplitWindow); - mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerWindow"), "Plandomizer Editor", ImVec2(850, 760)); + mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760)); gui->AddGuiWindow(mPlandomizerWindow); - mAdvancedResolutionSettingsWindow = std::make_shared(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599)); - gui->AddGuiWindow(mAdvancedResolutionSettingsWindow); mModalWindow = std::make_shared(CVAR_WINDOW("ModalWindow"), "Modal Window"); gui->AddGuiWindow(mModalWindow); mModalWindow->Show(); @@ -221,8 +189,6 @@ namespace SohGui { mNotificationWindow->Show(); mTimeDisplayWindow = std::make_shared(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers"); gui->AddGuiWindow(mTimeDisplayWindow); - mAboutWindow = std::make_shared(CVAR_WINDOW("AboutWindow"), "About"); - gui->AddGuiWindow(mAboutWindow); } void Destroy() { @@ -231,7 +197,6 @@ namespace SohGui { mNotificationWindow = nullptr; mModalWindow = nullptr; - mAdvancedResolutionSettingsWindow = nullptr; mRandomizerSettingsWindow = nullptr; mItemTrackerWindow = nullptr; mItemTrackerSettingsWindow = nullptr; @@ -259,7 +224,6 @@ namespace SohGui { mTimeSplitWindow = nullptr; mPlandomizerWindow = nullptr; mTimeDisplayWindow = nullptr; - mAboutWindow = nullptr; } void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index 4544c13e9..f465d1d1b 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -26,18 +26,8 @@ #include "soh/Enhancements/randomizer/randomizer_settings_window.h" #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" -#include "soh/AboutWindow.h" #include "SohModals.h" -#ifdef __cplusplus -extern "C" { -#endif - void enableBetaQuest(); - void disableBetaQuest(); -#ifdef __cplusplus -} -#endif - namespace SohGui { void SetupHooks(); void SetupGuiElements(); @@ -45,6 +35,9 @@ namespace SohGui { void Destroy(); void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); void ShowRandomizerSettingsMenu(); + UIWidgets::Colors GetMenuThemeColor(); } +#define THEME_COLOR SohGui::GetMenuThemeColor() + #endif /* SohGui_hpp */ diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp new file mode 100644 index 000000000..d4aee295a --- /dev/null +++ b/soh/soh/SohGui/SohMenu.cpp @@ -0,0 +1,168 @@ +#include "SohMenu.h" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include +#include "StringHelper.h" +#include +#include + +extern std::unordered_map warpPointSceneList; + +namespace SohGui { +extern std::shared_ptr mSohMenu; + +using namespace UIWidgets; + +void SohMenu::AddSidebarEntry(std::string sectionName, std::string sidebarName, uint32_t columnCount) { + assert(!sectionName.empty()); + assert(!sidebarName.empty()); + menuEntries.at(sectionName).sidebars.emplace(sidebarName, SidebarEntry{ .columnCount = columnCount }); + menuEntries.at(sectionName).sidebarOrder.push_back(sidebarName); +} + +WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType) { + assert(!widgetName.empty()); // Must be unique + assert(menuEntries.contains(pathInfo.sectionName)); // Section/header must already exist + assert(menuEntries.at(pathInfo.sectionName).sidebars.contains(pathInfo.sidebarName)); // Sidebar must already exist + std::unordered_map& sidebar = menuEntries.at(pathInfo.sectionName).sidebars; + uint8_t column = pathInfo.column; + if (sidebar.contains(pathInfo.sidebarName)) { + while (sidebar.at(pathInfo.sidebarName).columnWidgets.size() < column + 1) { + sidebar.at(pathInfo.sidebarName).columnWidgets.push_back({}); + } + } + SidebarEntry& entry = sidebar.at(pathInfo.sidebarName); + entry.columnWidgets.at(column).push_back({ .name = widgetName, .type = widgetType }); + WidgetInfo& widget = entry.columnWidgets.at(column).back(); + switch (widgetType) { + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + widget.options = std::make_shared(); + break; + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + widget.options = std::make_shared(); + break; + case WIDGET_BUTTON: + widget.options = std::make_shared(); + break; + case WIDGET_WINDOW_BUTTON: + widget.options = std::make_shared(); + break; + case WIDGET_COLOR_24: + case WIDGET_COLOR_32: + break; + case WIDGET_SEARCH: + case WIDGET_SEPARATOR: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_TEXT: + default: + widget.options = std::make_shared(); + } + return widget; +} + +SohMenu::SohMenu(const std::string& consoleVariable, const std::string& name) + : Menu(consoleVariable, name, 0, UIWidgets::Colors::LightBlue) { +} + +void SohMenu::InitElement() { + Ship::Menu::InitElement(); + AddMenuSettings(); + AddMenuEnhancements(); + AddMenuRandomizer(); +#ifdef ENABLE_REMOTE_CONTROL + AddMenuNetwork(); +#endif + AddMenuDevTools(); + + if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { + InsertSidebarSearch(); + } + + for (auto& initFunc : MenuInit::GetInitFuncs()) { + initFunc(); + } + + disabledMap = { + { DISABLE_FOR_NO_VSYNC, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync(); + }, + "Disabling VSync not supported" } }, + { DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen(); + }, + "Windowed Fullscreen not supported" } }, + { DISABLE_FOR_NO_MULTI_VIEWPORT, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports(); + }, + "Multi-viewports not supported" } }, + { DISABLE_FOR_NOT_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Available Only on DirectX" } }, + { DISABLE_FOR_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Not Available on DirectX" } }, + { DISABLE_FOR_MATCH_REFRESH_RATE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_SETTING("gMatchRefreshRate"), 0); }, + "Match Refresh Rate is Enabled" } }, + { DISABLE_FOR_ADVANCED_RESOLUTION_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, + "Advanced Resolution Enabled" } }, + { DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + { [](disabledInfo& info) -> bool { + return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); + }, + "Vertical Resolution Toggle Enabled" } }, + { DISABLE_FOR_LOW_RES_MODE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_LOW_RES_MODE, 0); }, "N64 Mode Enabled" } }, + { DISABLE_FOR_NULL_PLAY_STATE, + { [](disabledInfo& info) -> bool { return gPlayState == NULL; }, "Save Not Loaded" } }, + { DISABLE_FOR_DEBUG_MODE_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gDeveloperTools.DebugEnabled", 0); }, + "Debug Mode is Disabled" } }, + { DISABLE_FOR_FRAME_ADVANCE_OFF, + { [](disabledInfo& info) -> bool { return !(gPlayState != nullptr && gPlayState->frameAdvCtx.enabled); }, + "Frame Advance is Disabled" } }, + { DISABLE_FOR_ADVANCED_RESOLUTION_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, + "Advanced Resolution is Disabled" } }, + { DISABLE_FOR_VERTICAL_RESOLUTION_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); }, + "Vertical Resolution Toggle is Off" } }, + }; +} + +void SohMenu::UpdateElement() { + Ship::Menu::UpdateElement(); +} + +void SohMenu::Draw() { + Ship::Menu::Draw(); +} + +void SohMenu::DrawElement() { + Ship::Menu::DrawElement(); +} +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenu.h b/soh/soh/SohGui/SohMenu.h new file mode 100644 index 000000000..3e5f7a555 --- /dev/null +++ b/soh/soh/SohGui/SohMenu.h @@ -0,0 +1,227 @@ +#ifndef SOHMENU_H +#define SOHMENU_H + +#include +#include "UIWidgets.hpp" +#include "Menu.h" +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "soh/cvar_prefixes.h" +#include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/presets.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "variables.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +#ifdef __cplusplus +extern "C" { +#endif + void enableBetaQuest(); + void disableBetaQuest(); +#ifdef __cplusplus +} +#endif + +namespace SohGui { + +static const std::unordered_map menuThemeOptions = { + { UIWidgets::Colors::Red, "Red" }, + { UIWidgets::Colors::DarkRed, "Dark Red" }, + { UIWidgets::Colors::Orange, "Orange" }, + { UIWidgets::Colors::Green, "Green" }, + { UIWidgets::Colors::DarkGreen, "Dark Green" }, + { UIWidgets::Colors::LightBlue, "Light Blue" }, + { UIWidgets::Colors::Blue, "Blue" }, + { UIWidgets::Colors::DarkBlue, "Dark Blue" }, + { UIWidgets::Colors::Indigo, "Indigo" }, + { UIWidgets::Colors::Violet, "Violet" }, + { UIWidgets::Colors::Purple, "Purple" }, + { UIWidgets::Colors::Brown, "Brown" }, + { UIWidgets::Colors::Gray, "Gray" }, + { UIWidgets::Colors::DarkGray, "Dark Gray" }, +}; + +static const std::unordered_map textureFilteringMap = { + { FILTER_THREE_POINT, "Three-Point" }, + { FILTER_LINEAR, "Linear" }, + { FILTER_NONE, "None" }, +}; + +static const std::unordered_map logLevels = { + { DEBUG_LOG_TRACE, "Trace" }, { DEBUG_LOG_DEBUG, "Debug" }, { DEBUG_LOG_INFO, "Info" }, + { DEBUG_LOG_WARN, "Warn" }, { DEBUG_LOG_ERROR, "Error" }, { DEBUG_LOG_CRITICAL, "Critical" }, + { DEBUG_LOG_OFF, "Off" }, +}; + +static const std::unordered_map notificationPosition = { + { 0, "Top Left" }, { 1, "Top Right" }, { 2, "Bottom Left" }, { 3, "Bottom Right" }, { 4, "Hidden" }, +}; + +static const std::unordered_map dekuStickCheat = { + { DEKU_STICK_NORMAL, "Normal" }, + { DEKU_STICK_UNBREAKABLE, "Unbreakable" }, + { DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE, "Unbreakable + Always on Fire" } +}; + +static const std::unordered_map skipGetItemAnimationOptions = { + { SGIA_DISABLED, "Disabled" }, + { SGIA_JUNK, "Junk Items" }, + { SGIA_ALL, "All Items" } +}; + +static const std::unordered_map chestStyleMatchesContentsOptions = { + { CSMC_DISABLED, "Disabled" }, + { CSMC_BOTH, "Both" }, + { CSMC_TEXTURE, "Texture Only" }, + { CSMC_SIZE, "Size Only" } +}; + +static const std::unordered_map timeTravelOptions = { + { TIME_TRAVEL_DISABLED, "Disabled" }, + { TIME_TRAVEL_OOT, "Ocarina of Time" }, + { TIME_TRAVEL_ANY, "Any Ocarina" } +}; + +static const std::unordered_map sleepingWaterfallOptions = { + { WATERFALL_ALWAYS, "Always" }, + { WATERFALL_ONCE, "Once" }, + { WATERFALL_NEVER, "Never" } +}; + +static const std::unordered_map allPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, + { DAMAGE_TORTURE, "Pure Torture (128x)" }, + { DAMAGE_OHKO, "OHKO (256x)" } +}; + +static const std::unordered_map subPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, + { DAMAGE_TORTURE, "Pure Torture (128x)" }, +}; + +static const std::unordered_map subSubPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, +}; + +static const std::unordered_map bonkDamageValues = { + { BONK_DAMAGE_NONE, "No Damage" }, + { BONK_DAMAGE_QUARTER_HEART, "0.25 Hearts" }, + { BONK_DAMAGE_HALF_HEART, "0.5 Hearts" }, + { BONK_DAMAGE_1_HEART, "1 Heart" }, + { BONK_DAMAGE_2_HEARTS, "2 Hearts" }, + { BONK_DAMAGE_4_HEARTS, "4 Hearts" }, + { BONK_DAMAGE_8_HEARTS, "8 Hearts" }, + { BONK_DAMAGE_OHKO, "OHKO" } +}; + +static const std::unordered_map cursorAnywhereValues = { + { PAUSE_ANY_CURSOR_RANDO_ONLY, "Only in Rando" }, + { PAUSE_ANY_CURSOR_ALWAYS_ON, "Always" }, + { PAUSE_ANY_CURSOR_ALWAYS_OFF, "Never" } +}; + +static const std::unordered_map swordToggleModes = { + { SWORD_TOGGLE_NONE, "None" }, + { SWORD_TOGGLE_CHILD, "Child Toggle" }, + { SWORD_TOGGLE_BOTH_AGES, "Both Ages" } +}; + +static const std::unordered_map zFightingOptions = { + { ZFIGHT_FIX_DISABLED, "Disabled" }, + { ZFIGHT_FIX_CONSISTENT_VANISH, "Consistent Vanish" }, + { ZFIGHT_FIX_NO_VANISH, "No Vanish" } +}; + +static const std::unordered_map mirroredWorldModes = { + { MIRRORED_WORLD_OFF, "Disabled" }, + { MIRRORED_WORLD_ALWAYS, "Always" }, + { MIRRORED_WORLD_RANDOM, "Random" }, + { MIRRORED_WORLD_RANDOM_SEEDED, "Random (Seeded)" }, + { MIRRORED_WORLD_DUNGEONS_ALL, "Dungeons" }, + { MIRRORED_WORLD_DUNGEONS_VANILLA, "Dungeons (Vanilla)" }, + { MIRRORED_WORLD_DUNGEONS_MQ, "Dungeons (MQ)" }, + { MIRRORED_WORLD_DUNGEONS_RANDOM, "Dungeons Random" }, + { MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED, "Dungeons Random (Seeded)"} +}; + +static const std::unordered_map enemyRandomizerModes = { + { ENEMY_RANDOMIZER_OFF, "Disabled" }, + { ENEMY_RANDOMIZER_RANDOM, "Random" }, + { ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)"} +}; + +static const std::unordered_map debugSaveFileModes = { + { 0, "Off" }, { 1, "Vanilla" }, { 2, "Maxed" }, +}; + +static const std::unordered_map bootSequenceLabels = { + { BOOTSEQUENCE_DEFAULT, "Default" }, + { BOOTSEQUENCE_AUTHENTIC, "Authentic" }, + { BOOTSEQUENCE_FILESELECT, "File Select" } +}; + +static const std::unordered_map enhancementPresetList = { + { ENHANCEMENT_PRESET_DEFAULT, "Default" }, + { ENHANCEMENT_PRESET_VANILLA_PLUS, "Vanilla Plus" }, + { ENHANCEMENT_PRESET_ENHANCED, "Enhanced" }, + { ENHANCEMENT_PRESET_RANDOMIZER, "Randomizer" } +}; + +static const char* itemCountMessageCVars[3] = { + CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), + CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), + CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), +}; + +static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = { + "Gold Skulltula Tokens", + "Pieces of Heart", + "Heart Containers", +}; + +class SohMenu : public Ship::Menu { + public: + SohMenu(const std::string& consoleVariable, const std::string& name); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + + void AddSidebarEntry(std::string sectionName, std::string sidbarName, uint32_t columnCount); + WidgetInfo& AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType); + void AddMenuSettings(); + void AddMenuEnhancements(); + void AddMenuDevTools(); + void AddMenuRandomizer(); + void AddMenuNetwork(); + + private: + char mGitCommitHashTruncated[8]; + bool mIsTaggedVersion; +}; +} // namespace SohGui + +#endif // SOHMENU_H diff --git a/soh/soh/SohGui/SohMenuBar.cpp b/soh/soh/SohGui/SohMenuBar.cpp index 5fd7324fb..18fa8b53d 100644 --- a/soh/soh/SohGui/SohMenuBar.cpp +++ b/soh/soh/SohGui/SohMenuBar.cpp @@ -39,12 +39,10 @@ #include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h" #include "soh/Enhancements/randomizer/randomizer_item_tracker.h" #include "soh/Enhancements/randomizer/randomizer_settings_window.h" -#include "soh/Enhancements/resolution-editor/ResolutionEditor.h" #include "soh/Enhancements/enemyrandomizer.h" #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" -#include "soh/AboutWindow.h" // FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but // they don't work how I expect them to so I added that because it looked good when I eyeballed it @@ -81,55 +79,6 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large "Linear", "None" }; - static const char* chestStyleMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" }; - static const char* skipGetItemAnimationOptions[3] = { "Disabled", "Junk Items", "All Items" }; - static const char* skipForcedDialogOptions[4] = { "None", "Navi Only", "NPCs Only", "All" }; - static const char* sleepingWaterfallOptions[3] = { "Always", "Once", "Never" }; - static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" }; - static const char* mirroredWorldModes[9] = { - "Disabled", "Always", "Random", "Random (Seeded)", "Dungeons", - "Dungeons (Vanilla)", "Dungeons (MQ)", "Dungeons Random", "Dungeons Random (Seeded)", - }; - static const char* enemyRandomizerModes[3] = { "Disabled", "Random", "Random (Seeded)" }; - static const char* allPowers[9] = { - "Vanilla (1x)", - "Double (2x)", - "Quadruple (4x)", - "Octuple (8x)", - "Foolish (16x)", - "Ridiculous (32x)", - "Merciless (64x)", - "Pure Torture (128x)", - "OHKO (256x)" }; - static const char* subPowers[8] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6], allPowers[7] }; - static const char* subSubPowers[7] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6] }; - static const char* zFightingOptions[3] = { "Disabled", "Consistent Vanish", "No Vanish" }; - static const char* bootSequenceLabels[3] = { "Default", "Authentic", "File Select" }; - static const char* DebugSaveFileModes[3] = { "Off", "Vanilla", "Maxed" }; - static const char* DekuStickCheat[3] = { "Normal", "Unbreakable", "Unbreakable + Always on Fire" }; - static const char* bonkDamageValues[8] = { - "No Damage", - "0.25 Heart", - "0.5 Heart", - "1 Heart", - "2 Hearts", - "4 Hearts", - "8 Hearts", - "OHKO" - }; - static const char* timeTravelOptions[3] = { "Disabled", "Ocarina of Time", "Any Ocarina" }; - static const char* swordToggleModes[3] = { "Disabled", "Child Toggle", "Both Ages (May lead to unintended behaviour)"}; - static const char* itemCountMessageCVars[3] = { - CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), - CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), - CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), - }; - static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = { - "Gold Skulltula Tokens", - "Pieces of Heart", - "Heart Containers", - }; - extern "C" SaveContext gSaveContext; namespace SohGui { @@ -137,2058 +86,43 @@ namespace SohGui { std::unordered_map availableWindowBackendsMap; Ship::WindowBackend configWindowBackend; -void UpdateWindowBackendObjects() { - Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend(); - int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1); - if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) { - configWindowBackend = static_cast(configWindowBackendId); - } else { - configWindowBackend = runningWindowBackend; - } - - auto availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends(); - for (auto& backend : *availableWindowBackends) { - availableWindowBackendsMap[backend] = windowBackendNames[backend]; - } -} - -void DrawMenuBarIcon() { - static bool gameIconLoaded = false; - if (!gameIconLoaded) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Game_Icon", "textures/icons/gIcon.png"); - gameIconLoaded = true; - } - - if (Ship::Context::GetInstance()->GetWindow()->GetGui()->HasTextureByName("Game_Icon")) { -#ifdef __SWITCH__ - ImVec2 iconSize = ImVec2(20.0f, 20.0f); - float posScale = 1.0f; -#elif defined(__WIIU__) - ImVec2 iconSize = ImVec2(16.0f * 2, 16.0f * 2); - float posScale = 2.0f; -#else - ImVec2 iconSize = ImVec2(16.0f, 16.0f); - float posScale = 1.0f; -#endif - ImGui::SetCursorPos(ImVec2(5, 2.5f) * posScale); - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Game_Icon"), iconSize); - ImGui::SameLine(); - ImGui::SetCursorPos(ImVec2(25, 0) * posScale); - } -} - -extern std::shared_ptr mAboutWindow; - -void DrawShipMenu() { - if (ImGui::BeginMenu("Ship")) { - if (mAboutWindow) { - if (ImGui::MenuItem("About...")) { - mAboutWindow->Show(); - } - } - - UIWidgets::Spacer(0); - - if (ImGui::MenuItem("Hide Menu Bar", -#if !defined(__SWITCH__) && !defined(__WIIU__) - "F1" -#else - "[-]" -#endif - )) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuBar()->ToggleVisibility(); - } - UIWidgets::Spacer(0); -#if !defined(__SWITCH__) && !defined(__WIIU__) - if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { - Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); - } - UIWidgets::Spacer(0); -#endif - if (ImGui::MenuItem("Reset", -#ifdef __APPLE__ - "Command-R" -#elif !defined(__SWITCH__) && !defined(__WIIU__) - "Ctrl+R" -#else - "" -#endif - )) { - std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); - } -#if !defined(__SWITCH__) && !defined(__WIIU__) - UIWidgets::Spacer(0); - if (ImGui::MenuItem("Open App Files Folder")) { - std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); - SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); - } - UIWidgets::Spacer(0); - - if (ImGui::MenuItem("Quit")) { - Ship::Context::GetInstance()->GetWindow()->Close(); - } -#endif - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mInputEditorWindow; extern std::shared_ptr mGfxDebuggerWindow; -extern std::shared_ptr mInputViewer; -extern std::shared_ptr mInputViewerSettings; -extern std::shared_ptr mAdvancedResolutionSettingsWindow; void DrawSettingsMenu() { - if (ImGui::BeginMenu("Settings")) - { - if (ImGui::BeginMenu("Audio")) { - UIWidgets::PaddedEnhancementSliderInt("Master Volume: %d %%", "##Master_Vol", CVAR_SETTING("Volume.Master"), 0, 100, "", 100, true, false, true); - if (UIWidgets::PaddedEnhancementSliderInt("Main Music Volume: %d %%", "##Main_Music_Vol", CVAR_SETTING("Volume.MainMusic"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_BGM_MAIN, ((float)CVarGetInteger(CVAR_SETTING("Volume.MainMusic"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Sub Music Volume: %d %%", "##Sub_Music_Vol", CVAR_SETTING("Volume.SubMusic"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_BGM_SUB, ((float)CVarGetInteger(CVAR_SETTING("Volume.SubMusic"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Fanfare Volume: %d %%", "##Fanfare_Vol", CVAR_SETTING("Volume.Fanfare"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_FANFARE, ((float)CVarGetInteger(CVAR_SETTING("Volume.Fanfare"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Sound Effects Volume: %d %%", "##Sound_Effect_Vol", CVAR_SETTING("Volume.SFX"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_SFX, ((float)CVarGetInteger(CVAR_SETTING("Volume.SFX"), 100) / 100.0f)); - } - - static std::unordered_map audioBackendNames = { - { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, - { Ship::AudioBackend::SDL, "SDL" } - }; - - ImGui::Text("Audio API (Needs reload)"); - auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend(); - - if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::BeginCombo("##AApi", audioBackendNames[currentAudioBackend])) { - for (uint8_t i = 0; i < Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size(); i++) { - auto backend = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->data()[i]; - if (ImGui::Selectable(audioBackendNames[backend], backend == currentAudioBackend)) { - Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(backend); - } - } - ImGui::EndCombo(); - } - if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { - UIWidgets::ReEnableComponent(""); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Controller")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2 (12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (mInputEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Controller Mapping", CVarGetInteger(CVAR_WINDOW("ControllerConfiguration"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputEditorWindow->ToggleVisibility(); - } - } - UIWidgets::PaddedSeparator(); - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - #ifndef __SWITCH__ - UIWidgets::EnhancementCheckbox("Menubar Controller Navigation", CVAR_IMGUI_CONTROLLER_NAV); - UIWidgets::Tooltip("Allows controller navigation of the SOH menu bar (Settings, Enhancements,...)\nCAUTION: This will disable game inputs while the menubar is visible.\n\nD-pad to move between items, A to select, and X to grab focus on the menu bar"); - UIWidgets::PaddedSeparator(); - #endif - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2 (12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (mInputViewer) { - if (ImGui::Button(GetWindowButtonText("Input Viewer", CVarGetInteger(CVAR_WINDOW("InputViewer"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputViewer->ToggleVisibility(); - } - } - if (mInputViewerSettings) { - if (ImGui::Button(GetWindowButtonText("Input Viewer Settings", CVarGetInteger(CVAR_WINDOW("InputViewerSettings"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputViewerSettings->ToggleVisibility(); - } - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Graphics")) { - #ifndef __APPLE__ - const bool disabled_resolutionSlider = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); - if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %.1f %%", "##IMul", CVAR_INTERNAL_RESOLUTION, 0.5f, - 2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) { - Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); - } - UIWidgets::Tooltip("Resolution scale. Multiplies output resolution by this value, on each axis relative to window size.\n" - "Lower values may improve performance.\n" - "Values above 100% can be used for super-sampling, as an intensive but highly effective form of anti-aliasing.\n\n" - "Default: 100%"); - - if (mAdvancedResolutionSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - UIWidgets::Spacer(0); - if (ImGui::Button(GetWindowButtonText("Advanced Resolution", CVarGetInteger(CVAR_WINDOW("AdvancedResolutionEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mAdvancedResolutionSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - } - #else - // macOS: Internal resolution is currently disabled in libultraship. - ImGui::BeginGroup(); - ImGui::Text("Internal Resolution: 100.0%%"); - UIWidgets::Spacer(0); - ImGui::Text(" " ICON_FA_INFO_CIRCLE " Not available on this system."); - UIWidgets::Spacer(0); - ImGui::EndGroup(); - #endif - - #ifndef __WIIU__ - if (UIWidgets::PaddedEnhancementSliderInt( - (CVarGetInteger(CVAR_MSAA_VALUE, 1) == 1) ? "Anti-aliasing (MSAA): Off" : "Anti-aliasing (MSAA): %d", - "##IMSAA", CVAR_MSAA_VALUE, 1, 8, "", 1, true, true, false)) { - Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); - } - UIWidgets::Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of rendered geometry.\n" - "Higher sample count will result in smoother edges on models, but may reduce performance.\n\n" - "Recommended: 2x or 4x"); - #endif - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - { // FPS Slider - const int minFps = 20; - static int maxFps; - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - maxFps = 360; - } else { - maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - } - int currentFps = fmax(fmin(OTRGlobals::Instance->GetInterpolationFPS(), maxFps), minFps); - #ifdef __WIIU__ - UIWidgets::Spacer(0); - // only support divisors of 60 on the Wii U - if (currentFps > 60) { - currentFps = 60; - } else { - currentFps = 60 / (60 / currentFps); - } - - int fpsSlider = 1; - if (currentFps == 20) { - ImGui::Text("FPS: Original (20)"); - } else { - ImGui::Text("FPS: %d", currentFps); - if (currentFps == 30) { - fpsSlider = 2; - } else { // currentFps == 60 - fpsSlider = 3; - } - } - if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - if (ImGui::Button(" - ##WiiUFPS")) { - fpsSlider--; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - - UIWidgets::Spacer(0); - - ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - 60.0f), 260.0f)); - ImGui::SliderInt("##WiiUFPSSlider", &fpsSlider, 1, 3, "", ImGuiSliderFlags_AlwaysClamp); - ImGui::PopItemWidth(); - - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(" + ##WiiUFPS")) { - fpsSlider++; - } - - if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) { - UIWidgets::ReEnableComponent(""); - } - if (fpsSlider > 3) { - fpsSlider = 3; - } else if (fpsSlider < 1) { - fpsSlider = 1; - } - - if (fpsSlider == 1) { - currentFps = 20; - } else if (fpsSlider == 2) { - currentFps = 30; - } else if (fpsSlider == 3) { - currentFps = 60; - } - CVarSetInteger(CVAR_SETTING("InterpolationFPS"), currentFps); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - #else - bool matchingRefreshRate = - CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0) && Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != Ship::WindowBackend::FAST3D_DXGI_DX11; - UIWidgets::PaddedEnhancementSliderInt( - (currentFps == 20) ? "Frame Rate: Original (20 fps)" : "Frame Rate: %d fps", - "##FPSInterpolation", CVAR_SETTING("InterpolationFPS"), minFps, maxFps, "", 20, true, true, false, matchingRefreshRate); - #endif - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - UIWidgets::Tooltip( - "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics.\n" - "This is purely visual and does not impact game logic, execution of glitches etc.\n" - "Higher frame rate settings may impact CPU performance." - "\n\n " ICON_FA_INFO_CIRCLE - " There is no need to set this above your monitor's refresh rate. Doing so will waste resources and may give a worse result."); - } else { - UIWidgets::Tooltip( - "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics.\n" - "This is purely visual and does not impact game logic, execution of glitches etc.\n" - "Higher frame rate settings may impact CPU performance."); - } - } // END FPS Slider - - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - UIWidgets::Spacer(0); - if (ImGui::Button("Match Frame Rate to Refresh Rate")) { - int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - if (hz >= 20 && hz <= 360) { - CVarSetInteger(CVAR_SETTING("InterpolationFPS"), hz); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - } else { - UIWidgets::PaddedEnhancementCheckbox("Match Frame Rate to Refresh Rate", CVAR_SETTING("MatchRefreshRate"), true, false); - } - UIWidgets::Tooltip("Matches interpolation value to the game window's current refresh rate."); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - ImGui::Text("ImGui Menu Scale"); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, "(Experimental)"); - if (UIWidgets::EnhancementCombobox(CVAR_SETTING("ImGuiScale"), imguiScaleOptions, 1)) { - OTRGlobals::Instance->ScaleImGui(); - } - UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - ImGui::Text("Renderer API (Needs reload)"); - - if (availableWindowBackendsMap.size() <= 1) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::BeginCombo("##RApi", availableWindowBackendsMap[configWindowBackend])) { - for (auto backend : availableWindowBackendsMap) { - if (ImGui::Selectable(backend.second, backend.first == configWindowBackend)) { - Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", static_cast(backend.first)); - Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name", backend.second); - Ship::Context::GetInstance()->GetConfig()->Save(); - UpdateWindowBackendObjects(); - } - } - ImGui::EndCombo(); - } - if (availableWindowBackendsMap.size() <= 1) { - UIWidgets::ReEnableComponent(""); - } - - if (Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) { - UIWidgets::PaddedEnhancementCheckbox("Enable Vsync", CVAR_VSYNC_ENABLED, true, false); - UIWidgets::Tooltip("Activate vertical sync, to prevent screen tearing."); - } - - if (Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen()) { - UIWidgets::PaddedEnhancementCheckbox("Windowed fullscreen", CVAR_SDL_WINDOWED_FULLSCREEN, true, false); - } - - if (Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports()) { - UIWidgets::PaddedEnhancementCheckbox("Allow multi-windows (Needs reload)", CVAR_ENABLE_MULTI_VIEWPORTS, true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Allows windows to be able to be dragged off of the main game window. Requires a reload to take effect."); - } - - // If more filters are added to LUS, make sure to add them to the filters list here - ImGui::Text("Texture Filtering (Needs reload)"); - UIWidgets::EnhancementCombobox(CVAR_TEXTURE_FILTER, filters, FILTER_THREE_POINT); - UIWidgets::Tooltip("Texture filtering, aka texture smoothing. Requires a reload to take effect.\n\n" - "Three-Point: Replicates real N64 texture filtering.\n" - "Bilinear: If Three-Point causes poor performance, try this.\n" - "Nearest: Disables texture smoothing. (Not recommended)"); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Draw LUS settings menu (such as Overlays Text Font) - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Languages")) { - UIWidgets::PaddedEnhancementCheckbox("Translate Title Screen", CVAR_SETTING("TitleScreenTranslation")); - if (UIWidgets::EnhancementRadioButton("English", CVAR_SETTING("Languages"), LANGUAGE_ENG)) { - GameInteractor::Instance->ExecuteHooks(); - } - if (UIWidgets::EnhancementRadioButton("German", CVAR_SETTING("Languages"), LANGUAGE_GER)) { - GameInteractor::Instance->ExecuteHooks(); - } - if (UIWidgets::EnhancementRadioButton("French", CVAR_SETTING("Languages"), LANGUAGE_FRA)) { - GameInteractor::Instance->ExecuteHooks(); - } - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Accessibility")) { - #if defined(_WIN32) || defined(__APPLE__) - UIWidgets::PaddedEnhancementCheckbox("Text to Speech", CVAR_SETTING("A11yTTS")); - UIWidgets::Tooltip("Enables text to speech for in game dialog"); - #endif - UIWidgets::PaddedEnhancementCheckbox("Disable Idle Camera Re-Centering", CVAR_SETTING("A11yDisableIdleCam")); - UIWidgets::Tooltip("Disables the automatic re-centering of the camera when idle."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Notifications")) { - static const char* notificationPosition[] = { - "Top Left", - "Top Right", - "Bottom Left", - "Bottom Right", - "Hidden", - }; - - ImGui::Text("Position"); - UIWidgets::EnhancementCombobox(CVAR_SETTING("Notifications.Position"), notificationPosition, 0); - UIWidgets::EnhancementSliderFloat("Duration: %.1f seconds", "##NotificationDuration", CVAR_SETTING("Notifications.Duration"), 3.0f, 30.0f, "", 10.0f, false, true, false); - UIWidgets::EnhancementSliderFloat("BG Opacity: %.1f %%", "##NotificaitonBgOpacity", CVAR_SETTING("Notifications.BgOpacity"), 0.0f, 1.0f, "", 0.5f, true, true, false); - UIWidgets::EnhancementSliderFloat("Size: %.1f", "##NotificaitonSize", CVAR_SETTING("Notifications.Size"), 1.0f, 20.0f, "", 1.8f, false, true, false); - - UIWidgets::Spacer(0); - - if (ImGui::Button("Test Notification", ImVec2(-1.0f, 0.0f))) { - Notification::Emit({ - .message = (gPlayState != NULL ? SohUtils::GetSceneName(gPlayState->sceneNum) : "Hyrule") + " looks beautiful today!", - }); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mAudioEditorWindow; -extern std::shared_ptr mCosmeticsEditorWindow; -extern std::shared_ptr mGameplayStatsWindow; -extern std::shared_ptr mTimeSplitWindow; -extern std::shared_ptr mTimeDisplayWindow; - -void DrawEnhancementsMenu() { - if (ImGui::BeginMenu("Enhancements")) - { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - - DrawPresetSelector(PRESET_TYPE_ENHANCEMENTS); - - UIWidgets::PaddedSeparator(); - - if (ImGui::BeginMenu("Gameplay")) - { - if (ImGui::BeginMenu("Time Savers")) - { - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 8.0f); - ImGui::BeginTable("##timeSaversMenu", 2, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::Text("Speed-ups:"); - UIWidgets::PaddedSeparator(); - bool allSkipsChecked = - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); - bool someSkipsChecked = - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); - - ImGuiContext* g = ImGui::GetCurrentContext(); - ImGuiItemFlags backup_item_flags = g->CurrentItemFlags; - if (!allSkipsChecked && someSkipsChecked) g->CurrentItemFlags |= ImGuiItemFlags_MixedValue; - if (ImGui::Checkbox("All", &allSkipsChecked)) { - int32_t newValue = allSkipsChecked ? 1 : 0; - - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), newValue); - - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - g->CurrentItemFlags = backup_item_flags; - UIWidgets::PaddedEnhancementCheckbox("Skip Intro", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Entrance Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Story Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Song Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Boss Introductions", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Quick Boss Deaths", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip One Point Cutscenes (Chests, Door Unlocks, etc)", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Owl Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Misc Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Disable Title Card", CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Exclude Glitch-Aiding Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, 0); - UIWidgets::Tooltip("Don't skip cutscenes that are associated with useful glitches, currently this is only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS and the Box Skip One Point in Jabu"); - UIWidgets::PaddedEnhancementCheckbox("Skip Child Stealth", CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."); - UIWidgets::PaddedEnhancementCheckbox("Skip Tower Escape", CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Skip the tower escape sequence between Ganondorf and Ganon."); - - UIWidgets::PaddedText("Skip Get Item Animations", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), skipGetItemAnimationOptions, SGIA_DISABLED); - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) != SGIA_DISABLED) { - UIWidgets::EnhancementSliderFloat("Item Scale: %f", "##ItemScale", CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale"), 5.0f, 15.0f, "", 10.0f, false); - UIWidgets::Tooltip("The size of the item when it is picked up"); - } - - UIWidgets::PaddedText("Skip Forced Dialog", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"), skipForcedDialogOptions, FORCED_DIALOG_SKIP_NONE); - UIWidgets::Tooltip("Prevent forced conversations with Navi or other NPCs"); - - UIWidgets::PaddedEnhancementSliderInt("Text Speed: %dx", "##TEXTSPEED", CVAR_ENHANCEMENT("TextSpeed"), 1, 5, "", 1, true, false, true); - UIWidgets::PaddedEnhancementCheckbox("Skip Text", CVAR_ENHANCEMENT("SkipText"), false, true); - UIWidgets::Tooltip("Holding down B skips text"); - UIWidgets::PaddedEnhancementSliderInt("Slow Text Speed: %dx", "##SLOWTEXTSPEED", CVAR_ENHANCEMENT("SlowTextSpeed"), 1, 5, "", 1, true, false, true); - UIWidgets::Tooltip("Changes the speed of sections of text that normally are paced slower than the text surrounding it."); - if (ImGui::Button("Match Normal Text")) { - CVarSetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1)); - } - UIWidgets::Tooltip("Makes the speed of slow text match the normal text speed above."); - UIWidgets::PaddedEnhancementSliderFloat("King Zora Speed: %.2fx", "##MWEEPSPEED", CVAR_ENHANCEMENT("MweepSpeed"), 0.1f, 5.0f, "", 1.0f, false, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Vine/Ladder Climb speed +%d", "##CLIMBSPEED", CVAR_ENHANCEMENT("ClimbSpeed"), 0, 12, "", 0, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Block pushing speed +%d", "##BLOCKSPEED", CVAR_ENHANCEMENT("FasterBlockPush"), 0, 5, "", 0, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Crawl speed %dx", "##CRAWLSPEED", CVAR_ENHANCEMENT("CrawlSpeed"), 1, 4, "", 1, true, false, true); - UIWidgets::PaddedEnhancementCheckbox("Faster Heavy Block Lift", CVAR_ENHANCEMENT("FasterHeavyBlockLift"), false, false); - UIWidgets::Tooltip("Speeds up lifting silver rocks and obelisks"); - UIWidgets::PaddedEnhancementCheckbox("Skip Pickup Messages", CVAR_ENHANCEMENT("FastDrops"), true, false); - UIWidgets::Tooltip("Skip pickup messages for new consumable items and bottle swipes"); - UIWidgets::PaddedEnhancementCheckbox("Fast Ocarina Playback", CVAR_ENHANCEMENT("FastOcarinaPlayback"), true, false); - UIWidgets::Tooltip("Skip the part where the Ocarina playback is called when you play a song"); - UIWidgets::PaddedEnhancementCheckbox("Skip Magic Arrow Equip Animation", CVAR_ENHANCEMENT("SkipArrowAnimation"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Skip save confirmation", CVAR_ENHANCEMENT("SkipSaveConfirmation"), true, false); - UIWidgets::Tooltip("Skip the \"Game saved.\" confirmation screen"); - UIWidgets::PaddedEnhancementCheckbox("Faster Farore's Wind", CVAR_ENHANCEMENT("FastFarores"), true, false); - UIWidgets::Tooltip("Greatly decreases cast time of Farore's Wind magic spell."); - - ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::Text("Changes:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementSliderInt("Biggoron Forge Time: %d days", "##FORGETIME", CVAR_ENHANCEMENT("ForgeTime"), 0, 3, "", 3, true, false, true); - UIWidgets::Tooltip("Allows you to change the number of days it takes for Biggoron to forge the Biggoron Sword"); - UIWidgets::PaddedEnhancementCheckbox("Remember Save Location", CVAR_ENHANCEMENT("RememberSaveLocation"), false, false); - UIWidgets::Tooltip("When loading a save, places Link at the last entrance he went through.\n" - "This doesn't work if the save was made in grottos/fairy fountains or dungeons."); - UIWidgets::PaddedEnhancementCheckbox("Navi Timer Resets", CVAR_ENHANCEMENT("ResetNaviTimer"), true, false); - UIWidgets::Tooltip("Resets the Navi timer on scene change. If you have already talked to her, she will try and talk to you again, instead of needing a save warp or death. "); - UIWidgets::PaddedEnhancementCheckbox("No Skulltula Freeze", CVAR_ENHANCEMENT("SkulltulaFreeze"), true, false); - UIWidgets::Tooltip("Stops the game from freezing the player when picking up Gold Skulltulas"); - UIWidgets::PaddedEnhancementCheckbox("Nighttime GS Always Spawn", CVAR_ENHANCEMENT("NightGSAlwaysSpawn"), true, false); - UIWidgets::Tooltip("Nighttime Skulltulas will spawn during both day and night."); - UIWidgets::PaddedEnhancementCheckbox("Dampe Appears All Night", CVAR_ENHANCEMENT("DampeAllNight"), true, false); - UIWidgets::Tooltip("Makes Dampe appear anytime during the night, not just his usual working hours."); - UIWidgets::PaddedEnhancementCheckbox("Fast Chests", CVAR_ENHANCEMENT("FastChests"), true, false); - UIWidgets::Tooltip("Kick open every chest"); - UIWidgets::PaddedText("Chest size & texture matches contents", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), chestStyleMatchesContentsOptions, CSMC_DISABLED)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { - CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); - } - } - UIWidgets::Tooltip( - "Chest sizes and textures are changed to help identify the item inside.\n" - " - Major items: Large gold chests\n" - " - Lesser items: Large brown chests\n" - " - Junk items: Small brown chests\n" - " - Small keys: Small silver chest\n" - " - Boss keys: Vanilla size and texture\n" - " - Skulltula Tokens: Small skulltula chest\n" - "\n" - "NOTE: Textures will not apply if you are using a mod pack with a custom chest model." - ); - if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) != CSMC_DISABLED) { - UIWidgets::PaddedEnhancementCheckbox("Chests of Agony", CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), true, false); - UIWidgets::Tooltip("Only change the size/texture of chests if you have the Stone of Agony."); - } - UIWidgets::PaddedEnhancementCheckbox("Ask to Equip New Items", CVAR_ENHANCEMENT("AskToEquip"), true, false); - UIWidgets::Tooltip("Adds a prompt to equip newly-obtained swords, shields and tunics"); - UIWidgets::PaddedEnhancementCheckbox("Better Owl", CVAR_ENHANCEMENT("BetterOwl"), true, false); - UIWidgets::Tooltip("The default response to Kaepora Gaebora is always that you understood what he said"); - UIWidgets::PaddedEnhancementCheckbox("Exit Market at Night", CVAR_ENHANCEMENT("MarketSneak"), true, false); - UIWidgets::Tooltip("Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard next to the gate."); - bool randoLockedOverworldDoors = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LOCK_OVERWORLD_DOORS); - UIWidgets::PaddedEnhancementCheckbox("Shops and Games Always Open", CVAR_ENHANCEMENT("OpenAllHours"), true, false, randoLockedOverworldDoors, - "This is not compatible with the Locked Overworld Doors Randomizer option", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Shops and minigames are open both day and night. Requires scene reload to take effect."); - UIWidgets::PaddedEnhancementCheckbox("Link as default file name", CVAR_ENHANCEMENT("LinkDefaultName"), true, false); - UIWidgets::Tooltip("Allows you to have \"Link\" as a premade file name"); - UIWidgets::PaddedEnhancementCheckbox("Quit Fishing At Door", CVAR_ENHANCEMENT("QuitFishingAtDoor"), true, false); - UIWidgets::Tooltip("Fisherman asks if you want to quit at the door when you still have the rod"); - UIWidgets::PaddedText("Time Travel with the Song of Time", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeTravel"), timeTravelOptions, 0); - UIWidgets::Tooltip("Allows Link to freely change age by playing the Song of Time.\n" - "Time Blocks can still be used properly.\n\n" - "Requirements:\n" - "- Obtained the Ocarina of Time (depends on selection)\n" - "- Obtained the Song of Time\n" - "- Obtained the Master Sword\n" - "- Not within range of Time Block\n" - "- Not within range of Ocarina playing spots"); - UIWidgets::PaddedEnhancementCheckbox("Pause Warp", CVAR_ENHANCEMENT("PauseWarp"), true, false); - UIWidgets::Tooltip("Selection of warp song in pause menu initiates warp. Disables song playback."); - UIWidgets::PaddedEnhancementCheckbox("Skip water take breath animation", CVAR_ENHANCEMENT("SkipSwimDeepEndAnim"), true, false); - UIWidgets::Tooltip("Skips Link's taking breath animation after coming up from water. This setting does not interfere with getting items from underwater."); - bool forceSkipScarecrow = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_SCARECROWS_SONG); - static const char* forceSkipScarecrowText = "This setting is forcefully enabled because a savefile\nwith \"Skip Scarecrow Song\" is loaded"; - UIWidgets::PaddedEnhancementCheckbox("Skip Scarecrow Song", CVAR_ENHANCEMENT("InstantScarecrow"), true, false, - forceSkipScarecrow, forceSkipScarecrowText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song."); - bool forceSleepingWaterfallEnhancement = - IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN; - uint8_t forceSleepingWaterfallValue = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) + 1; - static const char* forceSleepingWaterfallText = - "This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded."; - UIWidgets::PaddedText("Play Zelda's Lullaby to open Sleeping Waterfall", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), - sleepingWaterfallOptions, 0, forceSleepingWaterfallEnhancement, - forceSleepingWaterfallText, forceSleepingWaterfallValue); - UIWidgets::Tooltip( - "Always: Link must always play Zelda's Lullaby to open " - "the waterfall entrance to Zora's Domain.\n" - "Once: Link only needs to play Zelda's Lullaby once to " - "open the waterfall; after that, it stays open permanently.\n" - "Never: Link never needs to play Zelda's Lullaby to open the " - "waterfall; he only needs to have learned it and have an ocarina." - ); - - ImGui::EndTable(); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Items")) - { - UIWidgets::PaddedEnhancementCheckbox("Equip Items on D-pad", CVAR_ENHANCEMENT("DpadEquips"), true, false); - UIWidgets::Tooltip("Equip items and equipment on the D-pad\nIf used with \"D-pad on Pause Screen\", you " - "must hold C-Up to equip instead of navigate"); - UIWidgets::PaddedEnhancementCheckbox("Instant Putaway", CVAR_ENHANCEMENT("InstantPutaway"), true, false); - UIWidgets::Tooltip("Allow Link to put items away without having to wait around"); - UIWidgets::PaddedEnhancementCheckbox("Instant Boomerang Recall", CVAR_ENHANCEMENT("FastBoomerang"), true, false); - UIWidgets::Tooltip("Instantly return the boomerang to Link by pressing its item button while it's in the air"); - UIWidgets::PaddedEnhancementCheckbox("Prevent Dropped Ocarina Inputs", CVAR_ENHANCEMENT("DpadNoDropOcarinaInput"), true, false); - UIWidgets::Tooltip("Prevent dropping inputs when playing the ocarina quickly"); - UIWidgets::PaddedText("Bunny Hood Effect", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("MMBunnyHood"), bunnyHoodOptions, BUNNY_HOOD_VANILLA); - UIWidgets::Tooltip( - "Wearing the Bunny Hood grants a speed increase like in Majora's Mask. The longer jump option is not accounted for in randomizer logic.\n\n" - "Also disables NPC's reactions to wearing the Bunny Hood." - ); - UIWidgets::PaddedEnhancementCheckbox("Masks Equippable as Adult", CVAR_ENHANCEMENT("AdultMasks"), true, false); - UIWidgets::Tooltip("Allows masks to be equipped normally from the pause menu as adult."); - UIWidgets::PaddedEnhancementCheckbox("Persistent masks", CVAR_ENHANCEMENT("PersistentMasks"), true, false); - UIWidgets::Tooltip( - "Stops masks from automatically unequipping on certain situations:\n" - "- When entering a new scene\n" - "- When not in any C button or the D-Pad\n" - "- When saving and quitting\n" - "- When dying\n" - "- When traveling thru time (if \"Masks Equippable as Adult\" is activated)" - ); - UIWidgets::PaddedEnhancementCheckbox("Mask Select in Inventory", CVAR_ENHANCEMENT("MaskSelect"), true, false); - UIWidgets::Tooltip("After completing the mask trading sub-quest, press A and any direction on the mask slot to change masks"); - UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", CVAR_ENHANCEMENT("NutsExplodeBombs"), true, false); - UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers."); - UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", CVAR_ENHANCEMENT("SeparateArrows"), true, false); - UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)"); - UIWidgets::PaddedEnhancementCheckbox("Bow as Child/Slingshot as Adult", CVAR_ENHANCEMENT("BowSlingshotAmmoFix"), true, false); - UIWidgets::Tooltip("Allows child to use bow with arrows.\nAllows adult to use slingshot with seeds.\n\nRequires glitches or 'Timeless Equipment' cheat to equip."); - UIWidgets::PaddedEnhancementCheckbox("Better Farore's Wind", CVAR_ENHANCEMENT("BetterFarore"), true, false); - UIWidgets::Tooltip("Helps FW persist between ages, gives child and adult separate FW points, and can be used in more places."); - UIWidgets::PaddedEnhancementCheckbox("Remove Explosive Limit", CVAR_ENHANCEMENT("RemoveExplosiveLimit"), true, false); - UIWidgets::Tooltip("Removes the cap of 3 active explosives being deployed at once."); - UIWidgets::PaddedEnhancementCheckbox("Static Explosion Radius", CVAR_ENHANCEMENT("StaticExplosionRadius"), true, false); - UIWidgets::Tooltip("Explosions are now a static size, like in Majora's Mask and OoT3D. Makes bombchu hovering much easier."); - UIWidgets::PaddedEnhancementCheckbox("Prevent Bombchus Forcing First-Person", CVAR_ENHANCEMENT("DisableFirstPersonChus"), true, false); - UIWidgets::Tooltip("Prevent bombchus from forcing the camera into first-person mode when released."); - UIWidgets::PaddedEnhancementCheckbox("Better Bombchu Shopping", CVAR_ENHANCEMENT("BetterBombchuShopping"), true, false, - IS_RANDO, "This setting is forcefully enabled when you are playing a randomizer.", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Bombchus do not sell out when bought, and a 10 pack of bombchus costs 99 rupees instead of 100." - "\n" - "Toggling while inside the shop will not change prices or restock any SOLD OUTs"); - UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for the bow/slingshot", CVAR_ENHANCEMENT("BowReticle"), true, false); - UIWidgets::Tooltip("Aiming with a bow or slingshot will display a reticle as with the hookshot when the projectile is ready to fire."); - if (UIWidgets::PaddedEnhancementCheckbox("Aim boomerang in first-person mode", CVAR_ENHANCEMENT("BoomerangFirstPerson"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { - CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); - } - } - UIWidgets::Tooltip( - "Change aiming for the boomerang from third person to first person to see past Link's head"); - if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for boomerang", CVAR_ENHANCEMENT("BoomerangReticle"), true, false); - UIWidgets::Tooltip("Aiming with the boomerang will display a reticle as with the hookshot"); - } - if (UIWidgets::PaddedEnhancementCheckbox("Allow strength equipment to be toggled", CVAR_ENHANCEMENT("ToggleStrength"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) { - CVarSetInteger(CVAR_ENHANCEMENT("StrengthDisabled"), 0); - } - } - UIWidgets::Tooltip("Allows strength to be toggled on and off by pressing A on the strength upgrade in the equipment subscreen of the pause menu (This allows performing some glitches that require the player to not have strength)."); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Item Count Messages")) { - int numOptions = ARRAY_COUNT(itemCountMessageCVars); - bool allItemCountsChecked = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [](const char* cvar) { return CVarGetInteger(cvar, 0); }); - bool someItemCountsChecked = std::any_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [](const char* cvar) { return CVarGetInteger(cvar, 0); }); - - ImGuiContext* g = ImGui::GetCurrentContext(); - ImGuiItemFlags backup_item_flags = g->CurrentItemFlags; - if (!allItemCountsChecked && someItemCountsChecked) g->CurrentItemFlags |= ImGuiItemFlags_MixedValue; - if (ImGui::Checkbox("All", &allItemCountsChecked)) { - int32_t newValue = allItemCountsChecked ? 1 : 0; - - std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); - - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - g->CurrentItemFlags = backup_item_flags; - - for (int i = 0; i < numOptions; i++) { - UIWidgets::PaddedEnhancementCheckbox(itemCountMessageOptions[i], itemCountMessageCVars[i], true, false); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Difficulty Options")) - { - if (ImGui::BeginMenu("Shooting Gallery")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeShootingGallery")); - UIWidgets::Tooltip("Turn on/off changes to the shooting gallery behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantShootingGalleryWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the shooting gallery minigame"); - UIWidgets::PaddedEnhancementCheckbox("No Rupee Randomization", CVAR_ENHANCEMENT("ConstantAdultGallery"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Forces the rupee order to not be randomized as adult, making it the same as chlid"); - UIWidgets::PaddedEnhancementSliderInt("Child Starting Ammunition: %d", "##cShootingGalleryAmmunition", CVAR_ENHANCEMENT("ShootingGalleryAmmoChild"), 10, 30, "", 15, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The ammunition at the start of the shooting gallery minigame as a child"); - UIWidgets::PaddedEnhancementSliderInt("Adult Starting Ammunition: %d", "##aShootingGalleryAmmunition", CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult"), 10, 30, "", 15, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The ammunition at the start of the shooting gallery minigame as an adult"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Bombchu Bowling")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeBombchuBowling")); - UIWidgets::Tooltip("Turn on/off changes to the bombchu bowling behavior"); - bool disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Remove Small Cucco", CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Prevents the small cucco from appearing in the bombchu bowling minigame"); - UIWidgets::PaddedEnhancementCheckbox("Remove Big Cucco", CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Prevents the big cucco from appearing in the bombchu bowling minigame"); - UIWidgets::PaddedEnhancementSliderInt("Bombchu Count: %d", "##cBombchuBowlingAmmunition", CVAR_ENHANCEMENT("BombchuBowlingAmmo"), 3, 20, "", 10, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The number of bombchus available at the start of the bombchu bowling minigame"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - if (ImGui::BeginMenu("Fishing")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeFishing")); - UIWidgets::Tooltip("Turn on/off changes to the fishing behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Fishing", CVAR_ENHANCEMENT("InstantFishing"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("All fish will be caught instantly"); - UIWidgets::PaddedEnhancementCheckbox("Guarantee Bite", CVAR_ENHANCEMENT("GuaranteeFishingBite"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("When a line is stable, guarantee bite. Otherwise use default logic"); - UIWidgets::PaddedEnhancementCheckbox("Fish Never Escape", CVAR_ENHANCEMENT("FishNeverEscape"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Once a hook has been set, fish will never let go while being reeled in."); - UIWidgets::PaddedEnhancementCheckbox("Loaches Always Appear", CVAR_ENHANCEMENT("LoachesAlwaysAppear"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Loaches will always appear in the fishing pond instead of every four visits."); - UIWidgets::PaddedEnhancementCheckbox("Skip Keep Confirmation", CVAR_ENHANCEMENT("SkipKeepConfirmation"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The pond owner will not ask to confirm if you want to keep a smaller fish."); - UIWidgets::PaddedEnhancementSliderInt("Child Minimum Weight: %d", "##cMinimumWeight", CVAR_ENHANCEMENT("MinimumFishWeightChild"), 3, 10, "", 10, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The minimum weight for the unique fishing reward as a child"); - UIWidgets::PaddedEnhancementSliderInt("Adult Minimum Weight: %d", "##aMinimumWeight", CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 6, 13, "", 13, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The minimum weight for the unique fishing reward as an adult"); - UIWidgets::PaddedEnhancementCheckbox("All fish are Hyrule Loaches", CVAR_ENHANCEMENT("AllHyruleLoaches"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Every fish in the fishing pond will always be a Hyrule Loach\n\nNote: This requires reloading the area"); - ImGui::EndMenu(); - } - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Lost Woods Ocarina Game")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeOcarinaGame")); - UIWidgets::Tooltip("Turn on/off changes to the lost woods ocarina game behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantOcarinaGameWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the lost woods ocarina game"); - UIWidgets::PaddedEnhancementSliderInt("Note Play Speed: %dx", "##OcarinaGameNoteSpeed", CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed"), 1, 5, "", 1, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the speed that the skull kids play notes"); - UIWidgets::PaddedEnhancementCheckbox("Unlimited Playback Time", CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Removes the timer to play back the song"); - UIWidgets::PaddedEnhancementSliderInt("Number of Starting Notes: %d", "##OcarinaGameStartingNotes", CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 1, 8, "", 3, true, true, false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes the skull kids play to start the first round"); - int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); - UIWidgets::PaddedEnhancementSliderInt("Round One Notes: %d", "##OcarinaGameRoundOne", - CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes"), roundMin, 8, "", 5, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the first round"); - UIWidgets::PaddedEnhancementSliderInt("Round Two Notes: %d", "##OcarinaGameRoundTwoNotes", - CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes"), roundMin, 8, "", 6, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the second round"); - UIWidgets::PaddedEnhancementSliderInt("Round Three Notes: %d", "##OcarinaGameRoundThreeNotes", - CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes"), roundMin, 8, "", 8, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the third round"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Frogs Ocarina Game")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")); - UIWidgets::Tooltip("Turn on/off changes to the frogs ocarina game behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0); - static const char* disabledTooltip = - "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantFrogsGameWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the frogs ocarina game"); - UIWidgets::PaddedEnhancementCheckbox("Unlimited Playback Time", CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Removes the timer to play back the song"); - bool disabledFrog = 0; - static const char* disabledFrogTooltip = - "This option is disabled because \"Customize Behavior\" is turned off or \"Unlimited Playback Time\" is on"; - if (!CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0)) { - disabledFrog = 1; - } else { - disabledFrog = 0; - } - UIWidgets::PaddedEnhancementSliderInt("Modify note timer: %dx", "##FrogsFailTimer", CVAR_ENHANCEMENT("FrogsModifyFailTime"), 1, 5, "", 1, true, true, false, - disabledFrog, disabledFrogTooltip); - UIWidgets::Tooltip("Adjusts the time allowed for playback before failing"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::PaddedEnhancementCheckbox("Delete File On Death", CVAR_ENHANCEMENT("DeleteFileOnDeath"), true, false); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - UIWidgets::Tooltip("Dying will delete your file\n\n " ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE "\nTHIS IS NOT REVERSABLE\nUSE AT YOUR OWN RISK!"); - ImGui::PopStyleColor(); - if (UIWidgets::PaddedEnhancementCheckbox("Permanent heart loss", CVAR_ENHANCEMENT("PermanentHeartLoss"), true, false)) { - UpdatePermanentHeartLossState(); - } - UIWidgets::Tooltip("When you lose 4 quarters of a heart you will permanently lose that heart container.\n\nDisabling this after the fact will restore your heart containers."); - ImGui::Text("Damage Multiplier"); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("DamageMult"), allPowers, 0); - UIWidgets::Tooltip( - "Modifies all sources of damage not affected by other sliders\n" - "2x: Can survive all common attacks from the start of the game\n" - "4x: Dies in 1 hit to any substantial attack from the start of the game\n" - "8x: Can only survive trivial damage from the start of the game\n" - "16x: Can survive all common attacks with max health without double defense\n" - "32x: Can survive all common attacks with max health and double defense\n" - "64x: Can survive trivial damage with max health without double defense\n" - "128x: Can survive trivial damage with max health and double defense\n" - "256x: Cannot survive damage" - ); - UIWidgets::PaddedText("Fall Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("FallDamageMult"), subPowers, 0); - UIWidgets::Tooltip( - "Modifies all fall damage\n" - "2x: Can survive all fall damage from the start of the game\n" - "4x: Can only survive short fall damage from the start of the game\n" - "8x: Cannot survive any fall damage from the start of the game\n" - "16x: Can survive all fall damage with max health without double defense\n" - "32x: Can survive all fall damage with max health and double defense\n" - "64x: Can survive short fall damage with double defense\n" - "128x: Cannot survive fall damage" - ); - UIWidgets::PaddedText("Void Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("VoidDamageMult"), subSubPowers, 0); - UIWidgets::Tooltip( - "Modifies damage taken after falling into a void\n" - "2x: Can survive void damage from the start of the game\n" - "4x: Cannot survive void damage from the start of the game\n" - "8x: Can survive void damage twice with max health without double defense\n" - "16x: Can survive void damage with max health without double defense\n" - "32x: Can survive void damage with max health and double defense\n" - "64x: Cannot survive void damage" - ); - UIWidgets::PaddedText("Bonk Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("BonkDamageMult"), bonkDamageValues, BONK_DAMAGE_NONE); - UIWidgets::Tooltip("Modifies damage taken after bonking."); - UIWidgets::PaddedEnhancementCheckbox("Spawn with full health", CVAR_ENHANCEMENT("FullHealthSpawn"), true, false); - UIWidgets::Tooltip("Respawn with full health instead of 3 Hearts"); - UIWidgets::PaddedEnhancementCheckbox("No Random Drops", CVAR_ENHANCEMENT("NoRandomDrops"), true, false); - UIWidgets::Tooltip("Disables random drops, except from the Goron Pot, Dampe, and bosses"); - bool forceEnableBombchuDrops = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_ENABLE_BOMBCHU_DROPS) == 1; - static const char* forceEnableBombchuDropsText = - "This setting is forcefully enabled because a savefile\nwith \"Enable Bombchu Drops\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Enable Bombchu Drops", CVAR_ENHANCEMENT("EnableBombchuDrops"), true, false, - forceEnableBombchuDrops, forceEnableBombchuDropsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Bombchus will sometimes drop in place of bombs"); - UIWidgets::PaddedEnhancementCheckbox("Trees Drop Sticks", CVAR_ENHANCEMENT("TreesDropSticks"), true, false); - UIWidgets::Tooltip("Bonking into trees will have a chance to drop up to 3 sticks. Must already have obtained sticks."); - UIWidgets::PaddedEnhancementCheckbox("No Heart Drops", CVAR_ENHANCEMENT("NoHeartDrops"), true, false); - UIWidgets::Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off\nThis simulates Hero Mode from other games in the series"); - if (UIWidgets::PaddedEnhancementCheckbox("Hyper Bosses", CVAR_ENHANCEMENT("HyperBosses"), true, false)) { - UpdateHyperBossesState(); - } - UIWidgets::Tooltip("All major bosses move and act twice as fast."); - if (UIWidgets::PaddedEnhancementCheckbox("Hyper Enemies", CVAR_ENHANCEMENT("HyperEnemies"), true, false)) { - UpdateHyperEnemiesState(); - } - UIWidgets::Tooltip("All regular enemies and mini-bosses move and act twice as fast."); - UIWidgets::PaddedEnhancementCheckbox("Always Win Goron Pot", CVAR_ENHANCEMENT("GoronPot"), true, false); - UIWidgets::Tooltip("Always get the heart piece/purple rupee from the spinning Goron pot"); - UIWidgets::PaddedEnhancementCheckbox("Always Win Dampe Digging Game", CVAR_ENHANCEMENT("DampeWin"), true, false); - UIWidgets::Tooltip("Always win the heart piece/purple rupee on the first dig in Dampe's grave digging game, just like in rando\nIn a rando file, this is unconditionally enabled"); - UIWidgets::PaddedEnhancementCheckbox("All Dogs are Richard", CVAR_ENHANCEMENT("AllDogsRichard"), true, false); - UIWidgets::Tooltip("All dogs can be traded in and will count as Richard."); - UIWidgets::PaddedEnhancementSliderInt("Cuccos Stay Put Multiplier: %dx", "##CuccoStayDurationMultiplier", CVAR_ENHANCEMENT("CuccoStayDurationMult"), 1, 5, "", 1, true, true, false); - UIWidgets::Tooltip("Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider."); - UIWidgets::PaddedEnhancementSliderInt("Leever Spawn Rate: %d seconds", "##LeeverSpawnRate", CVAR_ENHANCEMENT("LeeverSpawnRate"), 0, 10, "", 0, true, true, false); - UIWidgets::Tooltip("The time between leever groups spawning."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Reduced Clutter")) - { - UIWidgets::EnhancementCheckbox("Mute Low HP Alarm", CVAR_ENHANCEMENT("LowHpAlarm")); - UIWidgets::Tooltip("Disable the low HP beeping sound"); - UIWidgets::PaddedEnhancementCheckbox("Minimal UI", CVAR_ENHANCEMENT("MinimalUI"), true, false); - UIWidgets::Tooltip("Hides most of the UI when not needed\nNote: Doesn't activate until after loading a new scene"); - UIWidgets::PaddedEnhancementCheckbox("Disable Navi Call Audio", CVAR_ENHANCEMENT("DisableNaviCallAudio"), true, false); - UIWidgets::Tooltip("Disables the voice audio when Navi calls you"); - UIWidgets::PaddedEnhancementCheckbox("Disable Hot/Underwater Warning Text", CVAR_ENHANCEMENT("DisableTunicWarningText"), true, false); - UIWidgets::Tooltip("Disables warning text when you don't have on the Goron/Zora Tunic in Hot/Underwater conditions."); - UIWidgets::PaddedEnhancementCheckbox("Remember Minimap State Between Areas", CVAR_ENHANCEMENT("RememberMapToggleState")); - UIWidgets::Tooltip("Preserves the minimap visibility state when going between areas rather than defaulting it to \"on\" when going through loading zones."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::EnhancementCheckbox("Visual Stone of Agony", CVAR_ENHANCEMENT("VisualAgony")); - UIWidgets::Tooltip("Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble"); - static const char* cursorOnAnySlot[3] = { "Only in Rando", "Always", "Never" }; - UIWidgets::PaddedText("Allow the cursor to be on any slot", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("PauseAnyCursor"), cursorOnAnySlot, PAUSE_ANY_CURSOR_RANDO_ONLY); - UIWidgets::Tooltip("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select certain items."); - UIWidgets::PaddedEnhancementCheckbox("Assignable Tunics and Boots", CVAR_ENHANCEMENT("AssignableTunicsAndBoots"), true, false); - UIWidgets::Tooltip("Allows equipping the tunic and boots to c-buttons"); - UIWidgets::PaddedEnhancementCheckbox("Equipment Toggle", CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), true, false); - UIWidgets::Tooltip("Allows equipment to be removed by toggling it off on\nthe equipment subscreen."); - if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0)) { - UIWidgets::PaddedText("Sword Toggle Options", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SwordToggle"), swordToggleModes, SWORD_TOGGLE_NONE); - UIWidgets::Tooltip( - "Introduces Options for unequipping Link's sword\n\n" - "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the Master Sword.\n\n" - "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" - "Both Ages: Any sword can be unequipped as either age. This may lead to swordless glitches as Adult.\n" - ); - } - - UIWidgets::PaddedEnhancementCheckbox("Link's Cow in Both Time Periods", CVAR_ENHANCEMENT("CowOfTime"), true, false); - UIWidgets::Tooltip("Allows the Lon Lon Ranch obstacle course reward to be shared across time periods"); - UIWidgets::PaddedEnhancementCheckbox("Enable visible guard vision", CVAR_ENHANCEMENT("GuardVision"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Enable passage of time on file select", CVAR_ENHANCEMENT("TimeFlowFileSelect"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Pull grave during the day", CVAR_ENHANCEMENT("DayGravePull"), true, false); - UIWidgets::Tooltip("Allows graves to be pulled when child during the day"); - UIWidgets::PaddedEnhancementCheckbox("Dogs follow you everywhere", CVAR_ENHANCEMENT("DogFollowsEverywhere"), true, false); - UIWidgets::Tooltip("Allows dogs to follow you anywhere you go, even if you leave the market"); - UIWidgets::PaddedEnhancementCheckbox("Don't require input for Credits sequence", CVAR_ENHANCEMENT("NoInputForCredits"), true, false); - UIWidgets::Tooltip("Removes the input requirement on textboxes after defeating Ganon, allowing Credits sequence to continue to progress"); - UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", CVAR_ENHANCEMENT("NaviOnL"), true, false); - UIWidgets::Tooltip("Speak to Navi with L but enter first-person camera with C-Up"); - - // Blue Fire Arrows - bool forceEnableBlueFireArrows = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BLUE_FIRE_ARROWS); - static const char* forceEnableBlueFireArrowsText = - "This setting is forcefully enabled because a savefile\nwith \"Blue Fire Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Blue Fire Arrows", CVAR_ENHANCEMENT("BlueFireArrows"), true, false, - forceEnableBlueFireArrows, forceEnableBlueFireArrowsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Allows Ice Arrows to melt red ice.\nMay require a room reload if toggled during gameplay."); - - // Sunlight Arrows - bool forceEnableSunLightArrows = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SUNLIGHT_ARROWS); - static const char* forceEnableSunLightArrowsText = - "This setting is forcefully enabled because a savefile\nwith \"Sunlight Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Sunlight Arrows", CVAR_ENHANCEMENT("SunlightArrows"), true, false, - forceEnableSunLightArrows, forceEnableSunLightArrowsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Allows Light Arrows to activate sun switches.\nMay require a room reload if toggled during gameplay."); - - UIWidgets::PaddedEnhancementCheckbox("Disable Crit wiggle", CVAR_ENHANCEMENT("DisableCritWiggle"), true, false); - UIWidgets::Tooltip("Disable random camera wiggle at low health"); - UIWidgets::PaddedEnhancementCheckbox("Enemy Health Bars", CVAR_ENHANCEMENT("EnemyHealthBar"), true, false); - UIWidgets::Tooltip("Renders a health bar for enemies when Z-Targeted"); - - UIWidgets::PaddedEnhancementCheckbox("Targetable Hookshot Reticle", CVAR_ENHANCEMENT("HookshotableReticle"), true, false); - UIWidgets::Tooltip("Use a different color when aiming at hookshotable collision"); - - UIWidgets::PaddedEnhancementCheckbox("Faster Rupee Accumulator", CVAR_ENHANCEMENT("TimeSavers.FasterRupeeAccumulator"), true, false); - UIWidgets::Tooltip("Causes your wallet to fill and empty faster when you gain or lose money."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Graphics")) - { - if (ImGui::BeginMenu("Mods")) { - UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", CVAR_ENHANCEMENT("AltAssets"), false, false); - UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not."); - UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", CVAR_ENHANCEMENT("DisableBombBillboarding"), true, false); - UIWidgets::Tooltip("Disables bombs always rotating to face the camera. To be used in conjunction with mods that want to replace bombs with 3D objects."); - UIWidgets::PaddedEnhancementCheckbox("Disable Grotto Fixed Rotation", CVAR_ENHANCEMENT("DisableGrottoRotation"), true, false); - UIWidgets::Tooltip("Disables grottos rotating with the camera. To be used in conjunction with mods that want to replace grottos with 3D objects."); - UIWidgets::PaddedEnhancementCheckbox("Invisible Bunny Hood", CVAR_ENHANCEMENT("HideBunnyHood"), true, false); - UIWidgets::Tooltip("Turns Bunny Hood invisible while still maintaining its effects."); - UIWidgets::PaddedEnhancementCheckbox("Disable HUD Heart animations", CVAR_ENHANCEMENT("NoHUDHeartAnimation"), true, false); - UIWidgets::Tooltip("Disables the beating animation of the hearts on the HUD."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::PaddedEnhancementCheckbox("Animated Link in Pause Menu", CVAR_ENHANCEMENT("PauseMenuAnimatedLink"), false, false); - UIWidgets::PaddedEnhancementCheckbox("Disable LOD", CVAR_ENHANCEMENT("DisableLOD"), true, false); - UIWidgets::Tooltip( - "Turns off the Level of Detail setting, making models use their higher-poly variants at any distance"); - if (UIWidgets::EnhancementSliderInt("Increase Actor Draw Distance: %dx", "##IncreaseActorDrawDistance", - CVAR_ENHANCEMENT("DisableDrawDistance"), 1, 5, "", 1, true, false)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { - CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); - } - } - UIWidgets::Tooltip("Increases the range in which actors/objects are drawn"); - if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1) { - UIWidgets::PaddedEnhancementCheckbox("Kokiri Draw Distance", - CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), true, false); - UIWidgets::Tooltip("The Kokiri are mystical beings that fade into view when approached\nEnabling this " - "will remove their draw distance"); - } - UIWidgets::PaddedEnhancementCheckbox("Widescreen Actor Culling", CVAR_ENHANCEMENT("WidescreenActorCulling"), - true, false); - UIWidgets::Tooltip("Adjusts the horizontal culling plane to account for widescreen resolutions"); - UIWidgets::PaddedEnhancementCheckbox( - "Cull Glitch Useful Actors", CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors"), true, false, - !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && - CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1, - "Requires Actor Draw Distance to be increased or Widescreen Actor Culling enabled"); - UIWidgets::Tooltip( - "Exclude actors that are useful for glitches from the extended culling ranges.\n" - "Some actors may still draw in the extended ranges, but will not \"update\" so that certain " - "glitches that leverage the original culling requirements will still work.\n" - "\n" - "The following actors are excluded:\n" - "- White clothed Gerudos\n" - "- King Zora\n" - "- Gossip Stones\n" - "- Boulders\n" - "- Blue Warps\n" - "- Darunia\n" - "- Gold Skulltulas"); - if (UIWidgets::PaddedEnhancementCheckbox("Show Age-Dependent Equipment", CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), true, - false)) { - UpdatePatchHand(); - } - UIWidgets::Tooltip("Makes all equipment visible, regardless of Age."); - if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 1) { - UIWidgets::PaddedEnhancementCheckbox("Scale Adult Equipment as Child", CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild"), true, false); - UIWidgets::Tooltip("Scales all of the Adult Equipment, as well and moving some a bit, to fit on Child Link Better. May not work properly with some mods."); - } - UIWidgets::PaddedEnhancementCheckbox("N64 Mode", CVAR_LOW_RES_MODE, true, false); - UIWidgets::Tooltip("Sets aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution"); - UIWidgets::PaddedEnhancementCheckbox("Glitch line-up tick", CVAR_ENHANCEMENT("DrawLineupTick"), true, false); - UIWidgets::Tooltip("Displays a tick in the top center of the screen to help with glitch line-ups in SoH, as traditional UI based line-ups do not work outside of 4:3"); - UIWidgets::PaddedEnhancementCheckbox("Enable 3D Dropped items/projectiles", CVAR_ENHANCEMENT("NewDrops"), true, false); - UIWidgets::Tooltip("Change most 2D items and projectiles on the overworld to their 3D versions"); - UIWidgets::PaddedEnhancementCheckbox("Disable Black Bar Letterboxes", CVAR_ENHANCEMENT("DisableBlackBars"), true, false); - UIWidgets::Tooltip("Disables Black Bar Letterboxes during cutscenes and Z-targeting\nNote: there may be minor visual glitches that were covered up by the black bars\nPlease disable this setting before reporting a bug"); - UIWidgets::PaddedEnhancementCheckbox("Dynamic Wallet Icon", CVAR_ENHANCEMENT("DynamicWalletIcon"), true, false); - UIWidgets::Tooltip("Changes the rupee in the wallet icon to match the wallet size you currently have"); - UIWidgets::PaddedEnhancementCheckbox("Always show dungeon entrances", CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon"), true, false); - UIWidgets::Tooltip("Always shows dungeon entrance icons on the minimap"); - UIWidgets::PaddedEnhancementCheckbox("Show Gauntlets in First Person", CVAR_ENHANCEMENT("FirstPersonGauntlets"), true, false); - UIWidgets::Tooltip("Renders Gauntlets when using the Bow and Hookshot like in OOT3D"); - if (UIWidgets::PaddedEnhancementCheckbox("Color Temple of Time's Medallions", CVAR_ENHANCEMENT("ToTMedallionsColors"), true, false)) { - PatchToTMedallions(); - } - UIWidgets::Tooltip("When medallions are collected, the medallion imprints around the Master Sword pedestal in the Temple of Time will become colored"); - UIWidgets::PaddedEnhancementCheckbox("Show locked door chains on both sides of locked doors", CVAR_ENHANCEMENT("ShowDoorLocksOnBothSides"), true, false); - UIWidgets::PaddedText("Fix Vanishing Paths", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix"), zFightingOptions, ZFIGHT_FIX_DISABLED) && gPlayState != NULL) { - UpdateDirtPathFixState(gPlayState->sceneNum); - } - UIWidgets::Tooltip("Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" - "Consistent: Certain paths vanish the same way in all resolutions\n" - "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" - "This might affect other decal effects\n"); - UIWidgets::PaddedEnhancementSliderInt("Text Spacing: %d", "##TEXTSPACING", CVAR_ENHANCEMENT("TextSpacing"), 4, 6, "", 6, true, true, true); - UIWidgets::Tooltip("Space between text characters (useful for HD font textures)"); - UIWidgets::PaddedEnhancementCheckbox("More info in file select", CVAR_ENHANCEMENT("FileSelectMoreInfo"), true, false); - UIWidgets::Tooltip("Shows what items you have collected in the file select screen, like in N64 randomizer"); - UIWidgets::PaddedEnhancementCheckbox("Better ammo rendering in pause menu", CVAR_ENHANCEMENT("BetterAmmoRendering"), true, false); - UIWidgets::Tooltip("Ammo counts in the pause menu will work correctly regardless of the position of items in the inventory"); - UIWidgets::PaddedEnhancementCheckbox("Remove spin attack darkness", CVAR_ENHANCEMENT("RemoveSpinAttackDarkness"), true, false); - UIWidgets::Tooltip("Remove the darkness that appears when charging a spin attack"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Fixes")) - { - UIWidgets::EnhancementCheckbox("Fix L&R Pause menu", CVAR_ENHANCEMENT("FixMenuLR")); - UIWidgets::Tooltip("Makes the L and R buttons in the pause menu the same color"); - UIWidgets::PaddedEnhancementCheckbox("Fix L&Z Page switch in Pause menu", CVAR_ENHANCEMENT("NGCKaleidoSwitcher"), true, false); - UIWidgets::Tooltip("Makes L and R switch pages like on the GameCube\nZ opens the Debug Menu instead"); - UIWidgets::PaddedEnhancementCheckbox("Fix Dungeon entrances", CVAR_ENHANCEMENT("FixDungeonMinimapIcon"), true, false); - UIWidgets::Tooltip("Removes the dungeon entrance icon on the top-left corner of the screen when no dungeon is present on the current map"); - UIWidgets::PaddedEnhancementCheckbox("Fix Two Handed idle animations", CVAR_ENHANCEMENT("TwoHandedIdle"), true, false); - UIWidgets::Tooltip("Re-enables the two-handed idle animation, a seemingly finished animation that was disabled on accident in the original game"); - UIWidgets::PaddedEnhancementCheckbox("Fix the Gravedigging Tour Glitch", CVAR_ENHANCEMENT("GravediggingTourFix"), true, false, SaveManager::Instance->IsRandoFile(), - "This setting is always enabled in randomizer files", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads"); - UIWidgets::PaddedEnhancementCheckbox( - "Fix Deku Nut upgrade", CVAR_ENHANCEMENT("DekuNutUpgradeFix"), true, false, IS_RANDO, - "This setting is forcefully enabled when you are playing a randomizer.", - UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw"); - UIWidgets::PaddedEnhancementCheckbox("Fix Navi text HUD position", CVAR_ENHANCEMENT("NaviTextFix"), true, false); - UIWidgets::Tooltip("Correctly centers the Navi text prompt on the HUD's C-Up button"); - UIWidgets::PaddedEnhancementCheckbox("Fix Anubis fireballs", CVAR_ENHANCEMENT("AnubisFix"), true, false); - UIWidgets::Tooltip("Make Anubis fireballs do fire damage when reflected back at them with the Mirror Shield"); - if (UIWidgets::PaddedEnhancementCheckbox("Fix Megaton Hammer crouch stab", CVAR_ENHANCEMENT("CrouchStabHammerFix"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { - CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); - } - } - UIWidgets::Tooltip("Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally"); - if (CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Remove power crouch stab", CVAR_ENHANCEMENT("CrouchStabFix"), true, false); - UIWidgets::Tooltip("Make crouch stabbing always do the same damage as a regular slash"); - } - UIWidgets::PaddedEnhancementCheckbox("Fix credits timing", CVAR_ENHANCEMENT("CreditsFix"), true, false); - UIWidgets::Tooltip("Extend certain credits scenes so the music lines up properly with the visuals"); - UIWidgets::PaddedEnhancementCheckbox("Fix Gerudo Warrior's clothing colors", CVAR_ENHANCEMENT("GerudoWarriorClothingFix"), true, false); - UIWidgets::Tooltip("Prevent the Gerudo Warrior's clothes changing color when changing Link's tunic or using bombs in front of her"); - UIWidgets::PaddedEnhancementCheckbox("Fix Camera Drift", CVAR_ENHANCEMENT("FixCameraDrift"), true, false); - UIWidgets::Tooltip("Fixes camera slightly drifting to the left when standing still due to a math error"); - UIWidgets::PaddedEnhancementCheckbox("Fix Camera Swing", CVAR_ENHANCEMENT("FixCameraSwing"), true, false); - UIWidgets::Tooltip("Fixes camera getting stuck on collision when standing still, also fixes slight shift back in camera when stop moving"); - UIWidgets::PaddedEnhancementCheckbox("Fix Hanging Ledge Swing Rate", CVAR_ENHANCEMENT("FixHangingLedgeSwingRate"), true, false); - UIWidgets::Tooltip("Fixes camera swing rate when player falls off a ledge and camera swings around"); - UIWidgets::PaddedEnhancementCheckbox("Fix Missing Jingle after 5 Silver Rupees", CVAR_ENHANCEMENT("SilverRupeeJingleExtend"), true, false); - UIWidgets::Tooltip( - "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " - "Currently only relevant in Master Quest."); - if (UIWidgets::PaddedEnhancementCheckbox("Fix out of bounds textures", CVAR_ENHANCEMENT("FixTexturesOOB"), true, false)) { - ApplyAuthenticGfxPatches(); - } - UIWidgets::Tooltip("Fixes authentic out of bounds texture reads, instead loading textures with the correct size"); - UIWidgets::PaddedEnhancementCheckbox("Fix Poacher's Saw Softlock", CVAR_ENHANCEMENT("FixSawSoftlock"), true, false, CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0), - "This is disabled because it is forced on when Skip Text is enabled.", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled."); - UIWidgets::PaddedEnhancementCheckbox("Fix enemies not spawning near water", CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes"), true, false); - UIWidgets::Tooltip("Causes respawning enemies, like stalchildren, to appear on land near bodies of water. " - "Fixes an incorrect calculation that acted like water underneath ground was above it."); - UIWidgets::PaddedEnhancementCheckbox("Fix Bush Item Drops", CVAR_ENHANCEMENT("BushDropFix"), true, false); - UIWidgets::Tooltip("Fixes the bushes to drop items correctly rather than spawning undefined items."); - UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", CVAR_ENHANCEMENT("FixVineFall"), true, false); - UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges."); - UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping"), true, false); - UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping."); - UIWidgets::PaddedEnhancementCheckbox("Fix Darunia dancing too fast", CVAR_ENHANCEMENT("FixDaruniaDanceSpeed"), - true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in vanilla."); - UIWidgets::PaddedEnhancementCheckbox("Fix raised Floor Switches", CVAR_ENHANCEMENT("FixFloorSwitches"), true, false); - UIWidgets::Tooltip("Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire Temple. \n" - "This will lower them, making activating them easier"); - UIWidgets::PaddedEnhancementCheckbox("Fix Zora hint dialogue", CVAR_ENHANCEMENT("FixZoraHintDialogue"), true, false); - UIWidgets::Tooltip("Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur before moving King Zora rather than after"); - if (UIWidgets::PaddedEnhancementCheckbox("Fix hand holding Hammer", CVAR_ENHANCEMENT("FixHammerHand"), true, false)) { - UpdatePatchHand(); - } - UIWidgets::Tooltip("Fixes Adult Link having a backwards left hand when holding the Megaton Hammer."); - if (UIWidgets::PaddedEnhancementCheckbox( - "Fix Broken Giant's Knife bug", CVAR_ENHANCEMENT("FixBrokenGiantsKnife"), true, false, IS_RANDO, - "This setting is forcefully enabled when you are playing a randomizer.", - UIWidgets::CheckboxGraphics::Checkmark)) { - bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); - bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); - bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; - - if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { - func_800849EC(gPlayState); - } - } - UIWidgets::Tooltip("Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it"); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Restoration")) - { - UIWidgets::PaddedEnhancementCheckbox("Red Ganon blood", CVAR_ENHANCEMENT("RedGanonBlood"), true, false); - UIWidgets::Tooltip("Restore the original red blood from NTSC 1.0/1.1. Disable for green blood"); - UIWidgets::PaddedEnhancementCheckbox("Fish while hovering", CVAR_ENHANCEMENT("HoverFishing"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows casting the Fishing Rod while using the Hover Boots"); - UIWidgets::PaddedEnhancementCheckbox("N64 Weird Frames", CVAR_ENHANCEMENT("N64WeirdFrames"), true, false); - UIWidgets::Tooltip("Restores N64 Weird Frames allowing weirdshots to behave the same as N64"); - UIWidgets::PaddedEnhancementCheckbox("Bombchus out of bounds", CVAR_ENHANCEMENT("BombchusOOB"), true, false); - UIWidgets::Tooltip("Allows bombchus to explode out of bounds\nSimilar to GameCube and Wii VC"); - UIWidgets::PaddedEnhancementCheckbox("Quick Putaway", CVAR_ENHANCEMENT("QuickPutaway"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing Putaway Ocarina Items"); - UIWidgets::PaddedEnhancementCheckbox("Restore old Gold Skulltula cutscene", CVAR_ENHANCEMENT("GSCutscene"), true, false); - UIWidgets::Tooltip("Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die."); - UIWidgets::PaddedEnhancementCheckbox("Quick Bongo Kill", CVAR_ENHANCEMENT("QuickBongoKill"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him"); - UIWidgets::PaddedEnhancementCheckbox("Original RBA Values", CVAR_ENHANCEMENT("RestoreRBAValues"), true, false); - UIWidgets::Tooltip("Restores the original outcomes when performing Reverse Bottle Adventure."); - UIWidgets::PaddedEnhancementCheckbox("Early Eyeball Frog", CVAR_ENHANCEMENT("EarlyEyeballFrog"), true, false); - UIWidgets::Tooltip("Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora instead of the Zora Tunic by holding shield."); - UIWidgets::PaddedEnhancementCheckbox("Pulsate boss icon", CVAR_ENHANCEMENT("PulsateBossIcon"), true, false); - UIWidgets::Tooltip("Restores an unfinished feature to pulsate the boss room icon when you are in the boss room."); - UIWidgets::PaddedEnhancementSliderInt("Pause Buffer Input Window: %d", "##PauseBufferWindow", CVAR_ENHANCEMENT("PauseBufferWindow"), 0, 40, "", 0, true, true, false); - UIWidgets::PaddedEnhancementCheckbox("Include held inputs at start of Buffer Input Window", CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow"), true, false); - UIWidgets::Tooltip("Typically, inputs that are held prior to the buffer window are not included in the buffer. This setting changes that behavior to include them. This may cause some inputs to be re-triggered undesireably, for instance Z-Targetting something you might not want to."); - UIWidgets::PaddedEnhancementSliderInt("Simulated Input Lag: %d frames", "##SimulatedInputLag", CVAR_SIMULATED_INPUT_LAG, 0, 6, "", 0, true, true, false); - UIWidgets::Tooltip("Buffers your inputs to be executed a specified amount of frames later"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Extra Modes")) { - UIWidgets::PaddedText("Mirrored World", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("MirroredWorldMode"), mirroredWorldModes, MIRRORED_WORLD_OFF) && gPlayState != NULL) { - UpdateMirrorModeState(gPlayState->sceneNum); - } - UIWidgets::Tooltip( - "Mirrors the world horizontally\n\n" - "- Always: Always mirror the world\n" - "- Random: Randomly decide to mirror the world on each scene change\n" - "- Random (Seeded): Scenes are mirrored based on the current randomizer seed/file\n" - "- Dungeons: Mirror the world in Dungeons\n" - "- Dungeons (Vanilla): Mirror the world in vanilla Dungeons\n" - "- Dungeons (MQ): Mirror the world in MQ Dungeons\n" - "- Dungeons Random: Randomly decide to mirror the world in Dungeons\n" - "- Dungeons Random (Seeded): Dungeons are mirrored based on the current randomizer seed/file\n" - ); - - UIWidgets::PaddedText("Enemy Randomizer", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("RandomizedEnemies"), enemyRandomizerModes, ENEMY_RANDOMIZER_OFF)) { - GetSelectedEnemies(); - } - UIWidgets::Tooltip( - "Replaces fixed enemies throughout the game with a random enemy. Bosses, mini-bosses and a few specific regular enemies are excluded.\n" - "Enemies that need more than Deku Nuts + either Deku Sticks or a sword to kill are excluded from spawning in \"clear enemy\" rooms.\n\n" - "- Random: Enemies are randomized every time you load a room\n" - "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file\n" - ); - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) == ENEMY_RANDOMIZER_RANDOM) { - ImGui::Separator(); - if (ImGui::BeginMenu("Enemy List")) { - UIWidgets::PaddedEnhancementCheckbox("Select All Enemies", CVAR_ENHANCEMENT("RandomizedEnemyList.All"), true, false); - bool disabledEnemyList = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0); - ImGui::Separator(); - - ImGui::BeginDisabled(CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)); - - ImGui::BeginTable("Enemy Table", 2); - ImGui::TableNextColumn(); - for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { - if (UIWidgets::PaddedEnhancementCheckbox(enemyNameList[i], enemyCVarList[i], true, false, disabledEnemyList, - "These options are disabled because \"Select All Enemies\" is enabled.", - UIWidgets::CheckboxGraphics::Checkmark)) { - GetSelectedEnemies(); - } - if (i == RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE / 2) { - ImGui::TableNextColumn(); - } - } - ImGui::EndTable(); - ImGui::EndDisabled(); - - ImGui::EndMenu(); - } - }; - ImGui::Separator(); - - UIWidgets::PaddedEnhancementCheckbox("Randomized Enemy Sizes", CVAR_ENHANCEMENT("RandomizedEnemySizes"), true, false); - UIWidgets::Tooltip("Enemies and Bosses spawn with random sizes."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0)) { - UIWidgets::EnhancementCheckbox("Scale Health with Size", CVAR_ENHANCEMENT("EnemySizeScalesHealth")); - UIWidgets::Tooltip("Scales normal enemies health with their randomized size. *This will NOT affect bosses*"); - } - - UIWidgets::PaddedEnhancementCheckbox("Ivan the Fairy (Coop Mode)", CVAR_ENHANCEMENT("IvanCoopModeEnabled"), true, false); - UIWidgets::Tooltip("Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and " - "press the C-Buttons to use items and mess with Player 1!"); - - UIWidgets::PaddedEnhancementCheckbox("Rupee Dash Mode", CVAR_ENHANCEMENT("RupeeDash"), true, false); - UIWidgets::Tooltip("Rupees reduced over time, Link suffers damage when the count hits 0."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0)) { - UIWidgets::PaddedEnhancementSliderInt( - "Rupee Dash Interval: %d", "##DashInterval", CVAR_ENHANCEMENT("RupeeDashInterval"), 1, 10, "", 5, true, true, false, - !CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0), - "This option is disabled because \"Rupee Dash Mode\" is turned off"); - UIWidgets::Tooltip("Interval between Rupee reduction in Rupee Dash Mode"); - } - - UIWidgets::PaddedEnhancementCheckbox("Shadow Tag Mode", CVAR_ENHANCEMENT("ShadowTag"), true, false); - UIWidgets::Tooltip("A wallmaster follows Link everywhere, don't get caught!"); - - UIWidgets::PaddedEnhancementCheckbox("Additional Traps", CVAR_ENHANCEMENT("ExtraTraps.Enabled"), true, false); - UIWidgets::Tooltip("Enables additional Trap variants."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0)) { - UIWidgets::PaddedSeparator(); - if (ImGui::BeginMenu("Trap Options")) { - ImGui::Text("Tier 1 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Freeze Traps", CVAR_ENHANCEMENT("ExtraTraps.Ice"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Burn Traps", CVAR_ENHANCEMENT("ExtraTraps.Burn"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Shock Traps", CVAR_ENHANCEMENT("ExtraTraps.Shock"), true, false); - - UIWidgets::PaddedSeparator(); - ImGui::Text("Tier 2 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Knockback Traps", CVAR_ENHANCEMENT("ExtraTraps.Knockback"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Speed Traps", CVAR_ENHANCEMENT("ExtraTraps.Speed"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Bomb Traps", CVAR_ENHANCEMENT("ExtraTraps.Bomb"), true, false); - - UIWidgets::PaddedSeparator(); - ImGui::Text("Tier 3 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Void Traps", CVAR_ENHANCEMENT("ExtraTraps.Void"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Ammo Traps", CVAR_ENHANCEMENT("ExtraTraps.Ammo"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Death Traps", CVAR_ENHANCEMENT("ExtraTraps.Kill"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Teleport Traps", CVAR_ENHANCEMENT("ExtraTraps.Teleport"), true, false); - - ImGui::EndMenu(); - } - } - - UIWidgets::Spacer(0); - if (UIWidgets::PaddedEnhancementCheckbox("Hurt Container Mode", CVAR_ENHANCEMENT("HurtContainer"), true, false)) { - UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); - } - UIWidgets::Tooltip("Changes Heart Piece and Heart Container functionality.\n\n" - "- Each Heart Container or full Heart Piece reduces Links hearts by 1.\n" - "- Can be enabled retroactively after a File has already started."); - - ImGui::EndMenu(); - } - - UIWidgets::PaddedSeparator(); - - UIWidgets::EnhancementCheckbox("Autosave", CVAR_ENHANCEMENT("Autosave")); - UIWidgets::Tooltip("Save the game automatically on a 3 minute interval and when soft-resetting the game.\n\n" - "The interval autosave will wait if the game is paused in any way (dialogue, pause screen up, cutscenes)."); - - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - UIWidgets::PaddedText("Boot Sequence", false, true); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("BootSequence"), bootSequenceLabels, BOOTSEQUENCE_DEFAULT); - UIWidgets::Tooltip("Configure what happens when starting or resetting the game\n\n" - "Default: LUS logo -> N64 logo\n" - "Authentic: N64 logo only\n" - "File Select: Skip to file select menu" - ); - - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - ImGui::EndDisabled(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - - if (mCosmeticsEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Cosmetics Editor", CVarGetInteger(CVAR_WINDOW("CosmeticsEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mCosmeticsEditorWindow->ToggleVisibility(); - } - } - - if (mAudioEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Audio Editor", CVarGetInteger(CVAR_WINDOW("AudioEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mAudioEditorWindow->ToggleVisibility(); - } - } - - if (mGameplayStatsWindow) { - if (ImGui::Button(GetWindowButtonText("Gameplay Stats", CVarGetInteger(CVAR_WINDOW("GameplayStats"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mGameplayStatsWindow->ToggleVisibility(); - } - } - - if (mTimeSplitWindow) { - if (ImGui::Button(GetWindowButtonText("Time Splits", CVarGetInteger(CVAR_WINDOW("TimeSplitEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mTimeSplitWindow->ToggleVisibility(); - } - } - - if (mTimeDisplayWindow) { - if (ImGui::Button(GetWindowButtonText("Additional Timers", CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mTimeDisplayWindow->ToggleVisibility(); - } - } - if (mTimeDisplayWindow->IsVisible()) { - ImGui::SeparatorText("Timer Display Options"); - - if (!gPlayState) { - ImGui::Text("Additional Timer options\n" - "available when a file is\n" - "loaded..."); - } else { - if (UIWidgets::PaddedEnhancementSliderFloat("Font Scale: %.2fx", "##FontScale", CVAR_ENHANCEMENT("TimeDisplay.FontScale"), - 1.0f, 5.0f, "", 1.0f, false, true, false, true)) { - TimeDisplayInitSettings(); - } - if (UIWidgets::PaddedEnhancementCheckbox("Hide Background", CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), - false, false)) { - TimeDisplayInitSettings(); - } - ImGui::Separator(); - for (auto& timer : timeDisplayList) { - if (UIWidgets::PaddedEnhancementCheckbox(timer.timeLabel.c_str(), timer.timeEnable, false, false)) { - TimeDisplayUpdateDisplayOptions(); - } - } - } - } - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - #ifdef __SWITCH__ - UIWidgets::Spacer(0); - ImGui::Text("Switch performance mode"); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SwitchPerfMode"), SWITCH_CPU_PROFILES, (int)Ship::SwitchProfiles::STOCK)) { - SPDLOG_INFO("Profile:: %s", SWITCH_CPU_PROFILES[CVarGetInteger(CVAR_ENHANCEMENT("SwitchPerfMode"), (int)Ship::SwitchProfiles::STOCK)]); - Ship::Switch::ApplyOverclock(); - } - #endif - - ImGui::EndMenu(); - } -} - -void DrawCheatsMenu() { - if (ImGui::BeginMenu("Cheats")) - { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 8.0f); - ImGui::BeginTable("##cheatsMenu", 2, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextColumn(); - UIWidgets::Spacer(2.0f); - ImGui::Text("Inventory:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementCheckbox("Super Tunic", CVAR_CHEAT("SuperTunic"), true, false); - UIWidgets::Tooltip("Makes every tunic have the effects of every other tunic"); - UIWidgets::PaddedEnhancementCheckbox("Easy ISG", CVAR_CHEAT("EasyISG"), true, false); - UIWidgets::Tooltip("Passive Infinite Sword Glitch\nIt makes your sword's swing effect and hitbox stay active indefinitely"); - UIWidgets::PaddedEnhancementCheckbox("Easy QPA", CVAR_CHEAT("EasyQPA"), true, false); - UIWidgets::Tooltip("Gives you the glitched damage value of the quick put away glitch."); - UIWidgets::PaddedEnhancementCheckbox("Timeless Equipment", CVAR_CHEAT("TimelessEquipment"), true, false); - UIWidgets::Tooltip("Allows any item to be equipped, regardless of age\nAlso allows Child to use Adult strength upgrades"); - UIWidgets::PaddedEnhancementCheckbox("Unrestricted Items", CVAR_CHEAT("NoRestrictItems"), true, false); - UIWidgets::Tooltip("Allows you to use any item at any location"); - UIWidgets::PaddedEnhancementCheckbox("Fireproof Deku Shield", CVAR_CHEAT("FireproofDekuShield"), true, false); - UIWidgets::Tooltip("Prevents the Deku Shield from burning on contact with fire"); - UIWidgets::PaddedEnhancementCheckbox("Shield with Two-Handed Weapons", CVAR_CHEAT("ShieldTwoHanded"), true, false); - UIWidgets::Tooltip("This allows you to put up your shield with any two-handed weapon in hand except for Deku Sticks"); - UIWidgets::Spacer(2.0f); - ImGui::Text("Deku Sticks:"); - UIWidgets::EnhancementCombobox(CVAR_CHEAT("DekuStick"), DekuStickCheat, DEKU_STICK_NORMAL); - UIWidgets::Spacer(2.0f); - UIWidgets::EnhancementSliderFloat("Bomb Timer Multiplier: %.2fx", "##gBombTimerMultiplier", CVAR_CHEAT("BombTimerMultiplier"), 0.1f, 5.0f, "", 1.0f, false); - UIWidgets::PaddedEnhancementCheckbox("Hookshot Everything", CVAR_CHEAT("HookshotEverything"), true, false); - UIWidgets::Tooltip("Makes every surface in the game hookshot-able"); - UIWidgets::Spacer(0); - UIWidgets::EnhancementSliderFloat("Hookshot Reach Multiplier: %.2fx", "##gCheatHookshotReachMultiplier", CVAR_CHEAT("HookshotReachMultiplier"), 1.0f, 5.0f, "", 1.0f, false); - UIWidgets::Spacer(2.0f); - if (ImGui::Button("Change Age")) { - SwitchAge(); - } - UIWidgets::Tooltip("Switches Link's age and reloads the area."); - UIWidgets::Spacer(2.0f); - if (ImGui::Button("Clear Cutscene Pointer")) { - GameInteractor::RawAction::ClearCutscenePointer(); - } - UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps."); - - ImGui::TableNextColumn(); - UIWidgets::Spacer(2.0f); - - if (ImGui::BeginMenu("Infinite...")) { - UIWidgets::EnhancementCheckbox("Money", CVAR_CHEAT("InfiniteMoney")); - UIWidgets::PaddedEnhancementCheckbox("Health", CVAR_CHEAT("InfiniteHealth"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Ammo", CVAR_CHEAT("InfiniteAmmo"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Magic", CVAR_CHEAT("InfiniteMagic"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Nayru's Love", CVAR_CHEAT("InfiniteNayru"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Epona Boost", CVAR_CHEAT("InfiniteEponaBoost"), true, false); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Save States")) { - ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f }, " " ICON_FA_EXCLAMATION_TRIANGLE); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, " WARNING!!!! "); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f }, ICON_FA_EXCLAMATION_TRIANGLE); - UIWidgets::PaddedText("These are NOT like emulator states.", true, false); - UIWidgets::PaddedText("They do not save your game progress, and", true, false); - UIWidgets::PaddedText("they WILL break across transitions and", true, false); - UIWidgets::PaddedText("load zones (like doors). Support for", true, false); - UIWidgets::PaddedText("related issues will not be provided.", true, false); - if (UIWidgets::PaddedEnhancementCheckbox("I promise I have read the warning", CVAR_CHEAT("SaveStatePromise"), true, - false)) { - CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - if (CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 1) { - UIWidgets::PaddedEnhancementCheckbox("I understand, enable save states", CVAR_CHEAT("SaveStatesEnabled"), true, - false); - UIWidgets::Tooltip("F5 to save, F6 to change slots, F7 to load"); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(2.0f); - ImGui::Text("Behavior:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementCheckbox("No Clip", CVAR_CHEAT("NoClip"), true, false); - UIWidgets::Tooltip("Allows you to walk through walls"); - UIWidgets::PaddedEnhancementCheckbox("Climb Everything", CVAR_CHEAT("ClimbEverything"), true, false); - UIWidgets::Tooltip("Makes every surface in the game climbable"); - UIWidgets::PaddedEnhancementCheckbox("Moon Jump on L", CVAR_CHEAT("MoonJumpOnL"), true, false); - UIWidgets::Tooltip("Holding L makes you float into the air"); - UIWidgets::PaddedEnhancementCheckbox("New Easy Frame Advancing", CVAR_CHEAT("EasyFrameAdvance"), true, false); - UIWidgets::Tooltip("Continue holding START button when unpausing to only advance a single frame and then re-pause."); - UIWidgets::PaddedEnhancementCheckbox("Drops Don't Despawn", CVAR_CHEAT("DropsDontDie"), true, false); - UIWidgets::Tooltip("Drops from enemies, grass, etc. don't disappear after a set amount of time"); - UIWidgets::PaddedEnhancementCheckbox("Fish Don't despawn", CVAR_CHEAT("NoFishDespawn"), true, false); - UIWidgets::Tooltip("Prevents fish from automatically despawning after a while when dropped"); - UIWidgets::PaddedEnhancementCheckbox("Bugs Don't despawn", CVAR_CHEAT("NoBugsDespawn"), true, false); - UIWidgets::Tooltip("Prevents bugs from automatically despawning after a while when dropped"); - UIWidgets::PaddedEnhancementCheckbox("Freeze Time", CVAR_CHEAT("FreezeTime"), true, false); - UIWidgets::Tooltip("Freezes the time of day"); - UIWidgets::PaddedEnhancementCheckbox("Time Sync", CVAR_CHEAT("TimeSync"), true, false); - UIWidgets::Tooltip("This syncs the ingame time with the real world time"); - UIWidgets::PaddedEnhancementCheckbox("No ReDead/Gibdo Freeze", CVAR_CHEAT("NoRedeadFreeze"), true, false); - UIWidgets::Tooltip("Prevents ReDeads and Gibdos from being able to freeze you with their scream"); - UIWidgets::PaddedEnhancementCheckbox("Keese/Guay don't target you", CVAR_CHEAT("NoKeeseGuayTarget"), true, false); - UIWidgets::Tooltip("Keese and Guay no longer target you and simply ignore you as if you were wearing the skull mask"); - { - static int32_t betaQuestEnabled = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0); - static int32_t lastBetaQuestEnabled = betaQuestEnabled; - static int32_t betaQuestWorld = CVarGetInteger(CVAR_CHEAT("BetaQuestWorld"), 0xFFEF); - static int32_t lastBetaQuestWorld = betaQuestWorld; - - if (!isBetaQuestEnabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - UIWidgets::PaddedEnhancementCheckbox("Enable Beta Quest", CVAR_CHEAT("EnableBetaQuest"), true, false); - UIWidgets::Tooltip("Turns on OoT Beta Quest. *WARNING* This will reset your game."); - betaQuestEnabled = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0); - if (betaQuestEnabled) { - if (betaQuestEnabled != lastBetaQuestEnabled) { - betaQuestWorld = 0; - } - - ImGui::Text("Beta Quest World: %d", betaQuestWorld); - - if (ImGui::Button(" - ##BetaQuest")) { - betaQuestWorld--; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - - ImGui::SliderInt("##BetaQuest", &betaQuestWorld, 0, 8, "", ImGuiSliderFlags_AlwaysClamp); - UIWidgets::Tooltip("Set the Beta Quest world to explore. *WARNING* Changing this will reset your game.\nCtrl+Click to type in a value."); - - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(" + ##BetaQuest")) { - betaQuestWorld++; - } - - if (betaQuestWorld > 8) { - betaQuestWorld = 8; - } - else if (betaQuestWorld < 0) { - betaQuestWorld = 0; - } - } - else { - lastBetaQuestWorld = betaQuestWorld = 0xFFEF; - CVarClear(CVAR_CHEAT("BetaQuestWorld")); - } - if (betaQuestEnabled != lastBetaQuestEnabled || betaQuestWorld != lastBetaQuestWorld) - { - // Reset the game if the beta quest state or world changed because beta quest happens on redirecting the title screen cutscene. - lastBetaQuestEnabled = betaQuestEnabled; - lastBetaQuestWorld = betaQuestWorld; - CVarSetInteger(CVAR_CHEAT("EnableBetaQuest"), betaQuestEnabled); - CVarSetInteger(CVAR_CHEAT("BetaQuestWorld"), betaQuestWorld); - - std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - if (!isBetaQuestEnabled) { - UIWidgets::ReEnableComponent(""); - } - } - - ImGui::EndTable(); - ImGui::EndDisabled(); - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mStatsWindow; -extern std::shared_ptr mConsoleWindow; -extern std::shared_ptr mSaveEditorWindow; -extern std::shared_ptr mHookDebuggerWindow; -extern std::shared_ptr mColViewerWindow; -extern std::shared_ptr mActorViewerWindow; -extern std::shared_ptr mDLViewerWindow; -extern std::shared_ptr mValueViewerWindow; -extern std::shared_ptr mMessageViewerWindow; - -void DrawDeveloperToolsMenu() { - if (ImGui::BeginMenu("Developer Tools")) { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - - UIWidgets::EnhancementCheckbox("OoT Debug Mode", CVAR_DEVELOPER_TOOLS("DebugEnabled")); - UIWidgets::Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip with L + D-pad Right, and open the debug menu with L on the pause screen"); - if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0)) { - UIWidgets::EnhancementCheckbox("OoT Registry Editor", CVAR_DEVELOPER_TOOLS("RegEditEnabled")); - UIWidgets::Tooltip("Enables the registry editor"); - ImGui::Text("Debug Save File Mode:"); - UIWidgets::EnhancementCombobox(CVAR_DEVELOPER_TOOLS("DebugSaveFileMode"), DebugSaveFileModes, 1); - UIWidgets::Tooltip( - "Changes the behaviour of debug file select creation (creating a save file on slot 1 with debug mode on)\n" - "- Off: The debug save file will be a normal savefile\n" - "- Vanilla: The debug save file will be the debug save file from the original game\n" - "- Maxed: The debug save file will be a save file with all of the items & upgrades" - ); - } - UIWidgets::PaddedEnhancementCheckbox("OoT Skulltula Debug", CVAR_DEVELOPER_TOOLS("SkulltulaDebugEnabled"), true, false); - UIWidgets::Tooltip("Enables Skulltula Debug, when moving the cursor in the menu above various map icons (boss key, compass, map screen locations, etc) will set the GS bits in that area.\nUSE WITH CAUTION AS IT DOES NOT UPDATE THE GS COUNT."); - UIWidgets::PaddedEnhancementCheckbox("Better Debug Warp Screen", CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen"), true, false); - UIWidgets::Tooltip("Optimized debug warp screen, with the added ability to chose entrances and time of day"); - UIWidgets::PaddedEnhancementCheckbox("Debug Warp Screen Translation", CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation"), true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Translate the Debug Warp Screen based on the game language"); - UIWidgets::PaddedEnhancementCheckbox("Resource logging", CVAR_DEVELOPER_TOOLS("ResourceLogging"), true, false); - UIWidgets::Tooltip("Logs some resources as XML when they're loaded in binary format"); - if (gPlayState != NULL) { - UIWidgets::PaddedSeparator(); - ImGui::Checkbox("Frame Advance##frameAdvance", (bool*)&gPlayState->frameAdvCtx.enabled); - if (gPlayState->frameAdvCtx.enabled) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (ImGui::Button("Advance 1", ImVec2(ImGui::GetContentRegionAvail().x / 2.0f, 0.0f))) { - CVarSetInteger(CVAR_GENERAL("FrameAdvance"), 1); - } - ImGui::SameLine(); - ImGui::Button("Advance (Hold)"); - if (ImGui::IsItemActive()) { - CVarSetInteger(CVAR_GENERAL("FrameAdvance"), 1); - } - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - } - } - UIWidgets::PaddedSeparator(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (mStatsWindow) { - if (ImGui::Button(GetWindowButtonText("Stats", CVarGetInteger(CVAR_STATS_WINDOW_OPEN, 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mStatsWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Shows the stats window, with your FPS and frametimes, and the OS you're playing on"); - } - UIWidgets::Spacer(0); - if (mConsoleWindow) { - if (ImGui::Button(GetWindowButtonText("Console", CVarGetInteger(CVAR_CONSOLE_WINDOW_OPEN, 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mConsoleWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Enables the console window, allowing you to input commands, type help for some examples"); - } - UIWidgets::Spacer(0); - if (mSaveEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Save Editor", CVarGetInteger(CVAR_WINDOW("SaveEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mSaveEditorWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mHookDebuggerWindow) { - if (ImGui::Button(GetWindowButtonText("Hook Debugger", CVarGetInteger(CVAR_WINDOW("HookDebugger"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mHookDebuggerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mColViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Collision Viewer", CVarGetInteger(CVAR_WINDOW("CollisionViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mColViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mActorViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Actor Viewer", CVarGetInteger(CVAR_WINDOW("ActorViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mActorViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mDLViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Display List Viewer", CVarGetInteger(CVAR_WINDOW("DLViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mDLViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mValueViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Value Viewer", CVarGetInteger(CVAR_WINDOW("ValueViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mValueViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mMessageViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Message Viewer", CVarGetInteger(CVAR_WINDOW("MessageViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mMessageViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mGfxDebuggerWindow) { - if (ImGui::Button(GetWindowButtonText("Gfx Debugger", CVarGetInteger(CVAR_WINDOW("GfxDebugger"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mGfxDebuggerWindow->ToggleVisibility(); - } - } - - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - ImGui::EndDisabled(); - - ImGui::EndMenu(); - } -} - -#ifdef ENABLE_REMOTE_CONTROL -void DrawRemoteControlMenu() { - if (ImGui::BeginMenu("Network")) { - Sail::Instance->DrawMenu(); - CrowdControl::Instance->DrawMenu(); - ImGui::EndMenu(); - } -} -#endif - -extern std::shared_ptr mRandomizerSettingsWindow; -extern std::shared_ptr mPlandomizerWindow; -extern std::shared_ptr mItemTrackerWindow; -extern std::shared_ptr mItemTrackerSettingsWindow; -extern std::shared_ptr mEntranceTrackerWindow; -extern std::shared_ptr mEntranceTrackerSettingsWindow; -extern std::shared_ptr mCheckTrackerWindow; -extern std::shared_ptr mCheckTrackerSettingsWindow; -extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); - -void DrawRandomizerMenu() { - if (ImGui::BeginMenu("Randomizer")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - - #ifdef __WIIU__ - static ImVec2 buttonSize(200.0f * 2.0f, 0.0f); - static ImVec2 buttonWithOptionsSize(170.0f * 2.0f, 0.0f); - static ImVec2 optionsButtonSize(25.0f * 2.0f, 0.0f); - static float separationToOptionsButton = 5.0f * 2.0f; - #else - static ImVec2 buttonSize(200.0f, 0.0f); - static ImVec2 buttonWithOptionsSize(170.0f, 0.0f); - static ImVec2 optionsButtonSize(25.0f, 0.0f); - static float separationToOptionsButton = 5.0f; - #endif - - if (mRandomizerSettingsWindow) { - if (ImGui::Button(GetWindowButtonText("Randomizer Settings", CVarGetInteger(CVAR_WINDOW("RandomizerSettings"), 0)).c_str(), buttonSize)) { - mRandomizerSettingsWindow->ToggleVisibility(); - } - } - - UIWidgets::Spacer(0); - - if (mPlandomizerWindow) { - if (ImGui::Button(GetWindowButtonText("Plandomizer Editor", CVarGetInteger(CVAR_WINDOW("PlandomizerWindow"), 0)).c_str(), buttonSize)) { - mPlandomizerWindow->ToggleVisibility(); - } - } - - UIWidgets::Spacer(0); - - if (mItemTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Item Tracker", CVarGetInteger(CVAR_WINDOW("ItemTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mItemTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - ImVec2 cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mItemTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##ItemTrackerSettings", optionsButtonSize)) { - mItemTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - UIWidgets::Spacer(0); - if (mEntranceTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Entrance Tracker", CVarGetInteger(CVAR_WINDOW("EntranceTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mEntranceTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mEntranceTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##EntranceTrackerSettings", optionsButtonSize)) { - mEntranceTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - UIWidgets::Spacer(0); - - if (mCheckTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Check Tracker", CVarGetInteger(CVAR_WINDOW("CheckTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mCheckTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mCheckTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##CheckTrackerSettings", optionsButtonSize)) { - mCheckTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - UIWidgets::PaddedSeparator(); - - if (ImGui::BeginMenu("Rando Enhancements")) - { - UIWidgets::EnhancementCheckbox("Rando-Relevant Navi Hints", CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi"), false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "Replace Navi's overworld quest hints with rando-related gameplay hints." - ); - UIWidgets::PaddedEnhancementCheckbox("Random Rupee Names", CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames"), true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "When obtaining rupees, randomize what the rupee is called in the textbox." - ); - - UIWidgets::PaddedEnhancementCheckbox("Use Custom Key Models", CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), true, false); - UIWidgets::Tooltip("Use Custom graphics for dungeon keys, Big and Small, so that they can be easily told apart"); - - bool disableCompassColors = !DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS); - - static const char* disableCompassColorsText = - "This setting is disabled because a savefile is loaded without the compass\n" - "shuffle settings set to \"Any Dungeon\", \"Overworld\" or \"Anywhere\""; - - if (UIWidgets::PaddedEnhancementCheckbox("Compass Colors Match Dungeon", CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors"), true, false, - disableCompassColors, disableCompassColorsText, UIWidgets::CheckboxGraphics::Cross, true)) { - PatchCompasses(); - } - UIWidgets::Tooltip( - "Matches the color of compasses to the dungeon they belong to. " - "This helps identify compasses from afar and adds a little bit of flair.\n\nThis only " - "applies to seeds with compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"."); - - UIWidgets::PaddedEnhancementCheckbox("Quest Item Fanfares", CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares"), true, false); - UIWidgets::Tooltip( - "Play unique fanfares when obtaining quest items " - "(medallions/stones/songs). Note that these fanfares are longer than usual." - ); - UIWidgets::PaddedEnhancementCheckbox("Mysterious Shuffled Items", CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), true, false); - UIWidgets::Tooltip( - "Displays a \"Mystery Item\" model in place of any freestanding/GS/shop items that were shuffled, " - "and replaces item names for them and scrubs and merchants, regardless of hint settings, " - "so you never know what you're getting."); - UIWidgets::PaddedEnhancementCheckbox("Simpler Boss Soul Models", - CVAR_RANDOMIZER_ENHANCEMENT("SimplerBossSoulModels"), true, false); - UIWidgets::Tooltip( - "When shuffling boss souls, they'll appear as a simpler model instead of showing the boss' models." - "This might make boss souls more distinguishable from a distance, and can help with performance." - ); - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } + //if (ImGui::BeginMenu("Settings")) + //{ + // if (ImGui::BeginMenu("Graphics")) { + // ImGui::Text("ImGui Menu Scale"); + // ImGui::SameLine(); + // ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, "(Experimental)"); + // if (UIWidgets::EnhancementCombobox(CVAR_SETTING("ImGuiScale"), imguiScaleOptions, 1)) { + // OTRGlobals::Instance->ScaleImGui(); + // } + // UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); + + // // Draw LUS settings menu (such as Overlays Text Font) + // Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); + + // ImGui::EndMenu(); + // } + // ImGui::EndMenu(); + //} } void SohMenuBar::InitElement() { - UpdateWindowBackendObjects(); + } void SohMenuBar::DrawElement() { if (ImGui::BeginMenuBar()) { - DrawMenuBarIcon(); - static ImVec2 sWindowPadding(8.0f, 8.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, sWindowPadding); - DrawShipMenu(); - - ImGui::SetCursorPosY(0.0f); - DrawSettingsMenu(); ImGui::SetCursorPosY(0.0f); - DrawEnhancementsMenu(); - - ImGui::SetCursorPosY(0.0f); - - DrawCheatsMenu(); - - ImGui::SetCursorPosY(0.0f); - - DrawDeveloperToolsMenu(); - - ImGui::SetCursorPosY(0.0f); - - #ifdef ENABLE_REMOTE_CONTROL - DrawRemoteControlMenu(); - - ImGui::SetCursorPosY(0.0f); - #endif - - DrawRandomizerMenu(); - ImGui::PopStyleVar(1); ImGui::EndMenuBar(); } diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp new file mode 100644 index 000000000..917635f34 --- /dev/null +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -0,0 +1,170 @@ +#include "SohMenu.h" + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuDevTools() { + // Add Dev Tools Menu + AddMenuEntry("Dev Tools", CVAR_SETTING("Menu.DevToolsSidebarSection")); + + // General + AddSidebarEntry("Dev Tools", "General", 3); + WidgetPath path = { "Dev Tools", "General", SECTION_COLUMN_1 }; + + AddWidget(path, "Popout Menu", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Menu.Popout") + .Options(CheckboxOptions().Tooltip("Changes the menu display from overlay to windowed.")); + AddWidget(path, "Debug Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugEnabled")) + .Options(CheckboxOptions().Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip " + "with L + D-pad Right, and open the debug menu with L on the pause screen.")); + AddWidget(path, "OoT Registry Editor", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("RegEditEnabled")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(CheckboxOptions().Tooltip("Enables the registry editor.")); + AddWidget(path, "Debug Save File Mode", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugSaveFileMode")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(ComboboxOptions() + .Tooltip("Changes the behaviour of debug file select creation (creating a save file on slot 1 " + "with debug mode on)\n" + "- Off: The debug save file will be a normal savefile\n" + "- Vanilla: The debug save file will be the debug save file from the original game\n" + "- Maxed: The debug save file will be a save file with all of the items & upgrades") + .ComboMap(debugSaveFileModes)); + AddWidget(path, "OoT Skulltula Debug", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("SkulltulaDebugEnabled")) + .Options(CheckboxOptions().Tooltip("Enables Skulltula Debug, when moving the cursor in the menu above various " + "map icons (boss key, compass, map screen locations, etc) will set the GS " + "bits in that area.\nUSE WITH CAUTION AS IT DOES NOT UPDATE THE GS COUNT.")); + AddWidget(path, "Better Debug Warp Screen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen")) + .Options(CheckboxOptions().Tooltip( + "Optimized debug warp screen, with the added ability to chose entrances and time of day")); + AddWidget(path, "Debug Warp Screen Translation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation")) + .Options(CheckboxOptions() + .Tooltip("Translate the Debug Warp Screen based on the game language.") + .DefaultValue(true)); + AddWidget(path, "Resource logging", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("ResourceLogging")) + .Options(CheckboxOptions().Tooltip("Logs some resources as XML when they're loaded in binary format")); + + AddWidget(path, "Frame Advance", WIDGET_CHECKBOX) + .Options(CheckboxOptions().Tooltip( + "This allows you to advance through the game one frame at a time on command. " + "To advance a frame, hold Z and tap R on the second controller. Holding Z " + "and R will advance a frame every half second. You can also use the buttons below.")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + if (gPlayState != nullptr) { + info.valuePointer = (bool*)&gPlayState->frameAdvCtx.enabled; + } else { + info.valuePointer = (bool*)nullptr; + } + }); + AddWidget(path, "Advance 1", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Advance 1 frame.").Size(Sizes::Inline)) + .Callback([](WidgetInfo& info) { CVarSetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 1); }) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + }); + AddWidget(path, "Advance (Hold)", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Advance frames while the button is held.").Size(Sizes::Inline)) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + }) + .PostFunc([](WidgetInfo& info) { + if (ImGui::IsItemActive()) { + CVarSetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 1); + } + }) + .SameLine(true); + + // Stats + path.sidebarName = "Stats"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Stats Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("Stats")) + .WindowName("Stats") + .Options(WindowButtonOptions().Tooltip("Enables the separate Stats Window.")); + + // Console + path.sidebarName = "Console"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Console", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("Console")) + .WindowName("Console") + .Options(WindowButtonOptions().Tooltip("Enables the separate Console Window.")); + + // Save Editor + path.sidebarName = "Save Editor"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Save Editor", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("SaveEditor")) + .WindowName("Save Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Save Editor Window.")); + + // Hook Debugger + path.sidebarName = "Hook Debugger"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Hook Debugger", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("HookDebugger")) + .WindowName("Hook Debugger") + .Options(WindowButtonOptions().Tooltip("Enables the separate Hook Debugger Window.")); + + // Collision Viewer + path.sidebarName = "Collision Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Collision Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CollisionViewer")) + .WindowName("Collision Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Collision Viewer Window.")); + + // Actor Viewer + path.sidebarName = "Actor Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Actor Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ActorViewer")) + .WindowName("Actor Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Actor Viewer Window.")); + + // Display List Viewer + path.sidebarName = "DList Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Display List Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("DisplayListViewer")) + .WindowName("Display List Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Display List Viewer Window.")); + + // Value Viewer + path.sidebarName = "Value Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Value Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ValueViewer")) + .WindowName("Value Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Value Viewer Window.")); + + // Message Viewer + path.sidebarName = "Message Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Message Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("MessageViewer")) + .WindowName("Message Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Message Viewer Window.")); + + // Gfx Debugger + path.sidebarName = "Gfx Debugger"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("GfxDebugger")) + .WindowName("GfxDebuggerWindow") + .Options(WindowButtonOptions().Tooltip("Enables the separate Gfx Debugger Window.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp new file mode 100644 index 000000000..21a90d70b --- /dev/null +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -0,0 +1,2146 @@ +#include "SohMenu.h" +#include +#include +#include +#include +#include +#include +#include + +static std::string comboboxTooltip = ""; +static int32_t enhancementPresetSelected = ENHANCEMENT_PRESET_DEFAULT; +bool isBetaQuestEnabled = false; + +extern "C" { + void enableBetaQuest() { isBetaQuestEnabled = true; } + void disableBetaQuest() { isBetaQuestEnabled = false; } +} + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuEnhancements() { + // Add Enhancements Menu + AddMenuEntry("Enhancements", CVAR_SETTING("Menu.EnhancementsSidebarSection")); + + // Enhancements + WidgetPath path = { "Enhancements", "Presets", SECTION_COLUMN_1 }; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_ENHANCEMENTS); + for (auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter) { + if (iter->first != 0) comboboxTooltip += "\n\n"; + comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); + } + AddWidget(path, "Enhancement Presets", WIDGET_COMBOBOX) + .ValuePointer(&enhancementPresetSelected) + .Callback([](WidgetInfo& info) { + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); + CVarSetInteger(presetTypeCvar.c_str(), *std::get(info.valuePointer)); + }) + .Options(ComboboxOptions() + .ComboMap(enhancementPresetList) + .DefaultIndex(ENHANCEMENT_PRESET_DEFAULT) + .Tooltip(comboboxTooltip.c_str()) + ); + AddWidget(path, "Apply Preset##Enhancemnts", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_ENHANCEMENTS); + uint16_t selectedPresetId = CVarGetInteger(presetTypeCvar.c_str(), 0); + if(selectedPresetId >= presetTypeDef.presets.size()){ + selectedPresetId = 0; + } + const PresetDefinition selectedPresetDef = presetTypeDef.presets.at(selectedPresetId); + for(const char* block : presetTypeDef.blocksToClear) { + CVarClearBlock(block); + } + if (selectedPresetId != 0) { + applyPreset(selectedPresetDef.entries); + } + CVarSetInteger(presetTypeCvar.c_str(), selectedPresetId); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + path.sidebarName = "Gameplay"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + + AddWidget(path, "Boot Sequence", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BootSequence")) + .Options(ComboboxOptions() + .DefaultIndex(BOOTSEQUENCE_DEFAULT) + .ComboMap(bootSequenceLabels) + .Tooltip( + "Configure what happens when starting or resetting the game.\n\n" + "Default: LUS logo -> N64 logo\n" + "Authentic: N64 logo only\n" + "File Select: Skip to file select menu" + ) + ); + AddWidget(path, "Autosave", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Autosave")) + .Options(CheckboxOptions().Tooltip( + "Save the game automatically on a 3 minute interval and when soft-resetting the game. The interval " + "autosave will wait if the game is paused in any way (dialogue, pause screen up, cutscenes, " + "etc.).\n\n" + "The soft-reset save will *not* trigger in cutscene maps like the Chamber of Sages!" + )); + + AddWidget(path, "Audio", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Mute Low HP Alarm", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LowHPAlarm")) + .Options(CheckboxOptions().Tooltip( + "Disable the low HP beeping sound." + )); + AddWidget(path, "Disable Navi Call Audio", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableNaviCallAudio")) + .Options(CheckboxOptions().Tooltip( + "Disables the voice audio when Navi calls you." + )); + + AddWidget(path, "Pause Menu", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Allow the Cursor to be on any slot", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("PauseAnyCursor")) + .Options(ComboboxOptions() + .ComboMap(cursorAnywhereValues) + .DefaultIndex(PAUSE_ANY_CURSOR_RANDO_ONLY) + .Tooltip( + "Allows the cursor on the pause menu to be over any slot. Sometimes required in Randomizer " + "to select certain items." + ) + ); + AddWidget(path, "Assignable Tunics and Boots", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AssignableTunicsAndBoots")) + .Options(CheckboxOptions().Tooltip( + "Allows equipping the Tunics and Boots to C-Buttons/D-Pad." + )); + // TODO: Revist strength toggle, it's currently separate but should probably go here and be locked behind the + // Equipment toggle settings. Also maybe these should all be in Items sidebar? + AddWidget(path, "Equipment Toggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EquipmentCanBeRemoved")) + .Options(CheckboxOptions().Tooltip( + "Allows equipment to be removed by toggling it off on\n the equipment subscreen." + )); + AddWidget(path, "Sword Toggle Options", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SwordToggle")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0) == 0; + }) + .Options(ComboboxOptions() + .ComboMap(swordToggleModes) + .DefaultIndex(SWORD_TOGGLE_NONE) + .Tooltip( + "Introduces Options for unequipping Link's sword\n\n" + "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the Master Sword.\n\n" + "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" + "Both Ages: Any sword can be unequipped as either age. This may lead to swordless glitches as Adult." + ) + ); + + AddWidget(path, "Quality of Life", WIDGET_SEPARATOR_TEXT); + // Maybe should be in Timesavers somewhere? + AddWidget(path, "Link's Cow in Both Time Periods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CowOfTime")) + .Options(CheckboxOptions().Tooltip( + "Allows the Lon Lon Ranch Obstacle Course reward to be shared across time periods." + )); + // Maybe should be in Difficulty Options somewhere? + AddWidget(path, "Enable Visual Guard Vision", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GuardVision")); + // Maybe should be in TImesavers somewhere? + AddWidget(path, "Pull Grave during the day", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DayGravePull")) + .Options(CheckboxOptions().Tooltip( + "Allows graves to be pulled when child during the day." + )); + AddWidget(path, "Don't Require Input for Credits Sequence", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoInputForCredits")) + .Options(CheckboxOptions().Tooltip( + "Removes the Input Requirement on Text boxes after defeating Ganon, allowing the Credits " + "Sequence to continue to progress." + )); + AddWidget(path, "Answer Navi Prompt with L Button", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviOnL")) + .Options(CheckboxOptions().Tooltip( + "Speak to Navi with L but enter First-Person Camera with C-Up" + )); + AddWidget(path, "Disable Crit Wiggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableCritWiggle")) + .Options(CheckboxOptions().Tooltip( + "Disable Random Camera Wiggle at Low Health." + )); + AddWidget(path, "Targetable Hookshot Reticle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HookshotableReticle")) + .Options(CheckboxOptions().Tooltip( + "Makes the Hookshot Reticle use a different color when aiming at Hookshotable Collision." + )); + AddWidget(path, "Faster Rupee Accumulator", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterRupeeAccumulator")) + .Options(CheckboxOptions().Tooltip( + "Causes your Wallet to fill and empty faster when you gain or lose money." + )); + AddWidget(path, "Fun/Aesthetic Options", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Enable Passage of Time on File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")); + AddWidget(path, "Dogs Follow you Everywhere", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DogFollowsEverywhere")) + .Options(CheckboxOptions().Tooltip( + "Allows dogs to follow you anywhere you go, even if you leave the Market." + )); + AddWidget(path, "Enemy Health Bars", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemyHealthBar")) + .Options(CheckboxOptions().Tooltip( + "Renders a health bar for Enemies when Z-Targeted." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Authentic Bug Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix L&R Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixMenuLR")) + .Options(CheckboxOptions().Tooltip( + "Makes the L and R buttons in the pause menu the same color" + )); + AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NGCKaleidoSwitcher")) + .Options(CheckboxOptions().Tooltip( + "Makes L and R switch pages like on the Gamecube. Z opens the Debug Menu instead." + )); + AddWidget(path, "Fix Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip( + "Removes the Dungeon Entrance icon on the top-left corner of the screen when no dungeon is present on the " + "current map." + )); + AddWidget(path, "Fix Two-Handled Idle Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TwoHandedIdle")) + .Options(CheckboxOptions().Tooltip( + "Re-Enables the two-handed idle animation, a seemingly finished animation that was disabled on accident " + "in the original game." + )); + AddWidget(path, "Fix the Gravedigging Tour Glitch", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GravediggingTourFix")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is always enabled in randomized save files."; + }) + .Options(CheckboxOptions().Tooltip( + "Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads." + )); + AddWidget(path, "Fix Deku Nut Upgrade", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DekuNutUpgradeFix")) + .Options(CheckboxOptions().Tooltip( + "Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw." + )); + AddWidget(path, "Fix Navi Text HUD Position", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviTextFix")) + .Options(CheckboxOptions().Tooltip( + "Correctly centers the Navi text prompt on the HUD's C-Up button." + )); + AddWidget(path, "Fix Anubis Fireballs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AnubisFix")) + .Options(CheckboxOptions().Tooltip( + "Make Anubis Fireballs do Fire damage when reflected back at them with the Mirror Shield." + )); + AddWidget(path, "Fix Megaton Hammer Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabHammerFix")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { + CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); + } + }) + .Options(CheckboxOptions().Tooltip( + "Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally." + )); + AddWidget(path, "Remove Power Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabFix")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Make crouch stabbing always do the same damage as a regular slash." + )); + AddWidget(path, "Fix Credits Timing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CreditsFix")) + .Options(CheckboxOptions().Tooltip( + "Extend certain credits scenes so the music lines up properly with the visuals." + )); + AddWidget(path, "Fix Gerudo Warrior's Clothing Colors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GerudoWarriorClothingFix")) + .Options(CheckboxOptions().Tooltip( + "Prevent the Gerudo Warrior's clothes changign color when changing Link's tunic or " + "using Bombs in front of her." + )); + AddWidget(path, "Fix Camera Drift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraDrift")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera slightly drifting to the left when standing still due to a math error." + )); + AddWidget(path, "Fix Camera Swing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraSwing")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera getting stuck on collision when standing still. Also fixes slight shift " + "back in camera when Link stops moving." + )); + AddWidget(path, "Fix Hanging Ledge Swing Rate", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHangingLedgeSwingRate")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera swing rate when the player falls off a ledge and the camera swings around." + )); + AddWidget(path, "Fix Missing Jingle after 5 Silver Rupees", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SilverRupeeJingleExtend")) + .Options(CheckboxOptions().Tooltip( + "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " + "Only relevant for playthroughs involving Master Quest Dungeons." + )); + AddWidget(path, "Fix Out of Bounds Textures", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixTexturesOOB")) + .Callback([](WidgetInfo& info) { + ApplyAuthenticGfxPatches(); + }) + .Options(CheckboxOptions().Tooltip( + "Fixes authentic out of bounds texture reads, instead loading textures with the correct size." + )); + AddWidget(path, "Fix Poacher's Saw Softlock", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixSawSoftlock")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) == 1; + info.options->disabledTooltip = "This option is forced on when Skip Text is enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled." + )); + AddWidget(path, "Fix Enemies not Spawning Near Water", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes")) + .Options(CheckboxOptions().Tooltip( + "Causes respanwing enemies, like Stalchildren, to appear on land near bodies of water. " + "Fixes an incorrect calculation that acted like water underneath ground was above it." + )); + AddWidget(path, "Fix Bush Item Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BushDropFix")) + .Options(CheckboxOptions().Tooltip( + "Fixes the bushes to drop items correctly rather than spawning undefined items." + )); + AddWidget(path, "Fix Falling from Vine Edges", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixVineFall")) + .Options(CheckboxOptions().Tooltip( + "Prevents immediately falling off climbable surfaces if climbing on the edges." + )); + AddWidget(path, "Fix Link's Eyes Open while Sleeping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping")) + .Options(CheckboxOptions().Tooltip( + "Fixes Link's eyes being open in the openeing cutscene when he is supposed to be sleeping." + )); + AddWidget(path, "Fix Darunia Dancing Too Fast", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDaruniaDanceSpeed")) + .Options(CheckboxOptions().Tooltip( + "Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in the Original Game." + )); + AddWidget(path, "Fix Raised Floor Switches", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixFloorSwitches")) + .Options(CheckboxOptions().Tooltip( + "Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire " + "Temple. This will lower them, making activating them easier." + )); + AddWidget(path, "Fix Zora Hint Dialogue", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixZoraHintDialogue")) + .Options(CheckboxOptions().Tooltip( + "Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur " + "before moving King Zora rather than after." + )); + AddWidget(path, "Fix Hand Holding Hammer", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHammerHand")) + .Callback([](WidgetInfo& info) { + UpdatePatchHand(); + }) + .Options(CheckboxOptions().Tooltip( + "Fixes Adult Link having a backwards Left hand when holding the Megaton Hammer." + )); + AddWidget(path, "Fix Broken Giant's Knife Bug", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixGrokenGiantsKnife")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a Randomizer."; + }) + .Callback([](WidgetInfo& info) { + bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); + bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); + bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; + + if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { + func_800849EC(gPlayState); + } + }) + .Options(CheckboxOptions().Tooltip( + "Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it." + )); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Restorations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Red Ganon Blood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RedGanonBlood")) + .Options(CheckboxOptions().Tooltip( + "Restore the original red blood from NTSC 1.0/1.1. Disable for Green blood." + )); + AddWidget(path, "Fish while Hovering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HoverFishing")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NSTC 1.0 that allows casting the Fishing Rod while using the Hover Boots." + )); + AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) + .Options(CheckboxOptions().Tooltip( + "Restores N64 Weird Frames allwing weirdshots to behave the same as N64." + )); + AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchusOOB")) + .Options(CheckboxOptions().Tooltip( + "Allows Bombchus to explode out of bounds. Similar to Gamecube and Wii VC" + )); + AddWidget(path, "Quick Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickPutaway")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing " + "Putaway Ocarina Items." + )); + AddWidget(path, "Restore Old Gold Skulltula Cutscene", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GSCutscene")) + .Options(CheckboxOptions().Tooltip( + "Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die." + )); + AddWidget(path, "Quick Bongo Kill", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickBongoKill")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him." + )); + AddWidget(path, "Original RBA Values", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RestoreRBAValues")) + .Options(CheckboxOptions().Tooltip( + "Restores the original outcomes when performing Reverse Bottle Adventure." + )); + AddWidget(path, "Early Eyeball Frog", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EarlyEyeballFrog")) + .Options(CheckboxOptions().Tooltip( + "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " + "instead of the Zora Tunic by Holding Shield." + )); + AddWidget(path, "Pulsate Boss Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Pulsate Boss Icon")) + .Options(CheckboxOptions().Tooltip( + "Restores an unfinished feature to pulsate the boss room icon when you are in the boss room." + )); + AddWidget(path, "Pause Buffer Input Window: %d frames", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("PauseBufferWindow")) + .Options(IntSliderOptions() + .Min(0) + .Max(40) + .DefaultValue(0) + .Format("%d frames") + ); + AddWidget(path, "Include Held Inputs at the Start of Buffer Input Window", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow")) + .Options(CheckboxOptions().Tooltip( + "Typically, inputs that are held prior to the buffer window are not included in the buffer. This " + "setting changes that behavior to include them. This may cause some inputs to be re-triggered " + "undesireably, for instance Z-Targetting something you might not want to." + )); + AddWidget(path, "Simulated Input Lag: %d frames", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SIMULATED_INPUT_LAG) + .Options(IntSliderOptions() + .Min(0) + .Max(6) + .DefaultValue(0) + .Format("%d frames") + .Tooltip( + "Buffers your inputs to be executed a specified amount of frames later." + ) + ); + + path.sidebarName = "Time Savers"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Cutscenes", WIDGET_SEPARATOR_TEXT); + bool allSkipsChecked = false; + AddWidget(path, "Skip All", WIDGET_CHECKBOX) + .ValuePointer(&allSkipsChecked) + .PreFunc([](WidgetInfo& info) { + *std::get(info.valuePointer) = + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); + }) + .Callback([](WidgetInfo& info) { + int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; + + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), newValue); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + AddWidget(path, "Skip Intro", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Entrance Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Story Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Song Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Boss Introductions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Quick Boss Deaths", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip One Point Cutscenes (Chests, Door Unlocks, etc.)", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Owl Interactions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Misc Interactions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Disable Title Card", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Exclude Glitch-Aiding Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding")) + .Options(CheckboxOptions().Tooltip( + "Don't skip cutscenes that are associated wiht useful glitches. Currently, it is " + "only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS, and the Box Skip One " + "Point in Jabu." + )); + AddWidget(path, "Skip Child Stealth", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth")) + .Options(CheckboxOptions().Tooltip( + "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards." + )); + AddWidget(path, "Skip Tower Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape")) + .Options(CheckboxOptions().Tooltip( + "Skip the tower escape sequence between Ganondorf and Ganon." + )); + AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_DISABLED; + }) + .Options(FloatSliderOptions() + .Min(5.0f) + .Max(15.0f) + .Format("%.2f") + .DefaultValue(10.0f) + .Tooltip( + "The size of the item when it is picked up" + )); + + AddWidget(path, "Text", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog")) + .Options(CheckboxOptions().Tooltip( + "Prevent forced conversations with Navi or other NPCs." + )); + AddWidget(path, "Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + ); + AddWidget(path, "Skip Text", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipText")) + .Options(CheckboxOptions().Tooltip("Holding down B skips text.")); + AddWidget(path, "Slow Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("SlowTextSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Changes the speed of sections of text that normally are paced slower than the text surrounding it." + ) + ); + AddWidget(path, "Match Normal Text", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1)); + }) + .Options(ButtonOptions().Tooltip( + "Makes the speed of the slow text match the normal text speed above." + )); + AddWidget(path, "Skip Pickup Messages", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastDrops")) + .Options(CheckboxOptions().Tooltip( + "Skip Pickup Messages for new Consumable Items and Bottle Swipes." + )); + AddWidget(path, "Better Owl", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterOwl")) + .Options(CheckboxOptions().Tooltip( + "The default response to Kaepora Gaebora is always that you understood what he said." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Gameplay", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Save Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSaveConfirmation")) + .Options(CheckboxOptions().Tooltip("Skip the \"Game Saved\" confirmation screen.")); + AddWidget(path, "Biggoron Forge Time: %d days", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ForgeTime")) + .Options(IntSliderOptions() + .Min(0) + .Max(3) + .DefaultValue(3) + .Format("%d days") + .Tooltip( + "Allows you to change the number of days it takes for " + "Biggoron to forge the Biggoron's Sword." + ) + ); + AddWidget(path, "Remember Save Location", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberSaveLocation")) + .Options(CheckboxOptions().Tooltip( + "When loading a save, places Link at the last entrance he went through.\n" + "This doesn't work if the save was made in grottos, fairy fountains, or dungeons." + )); + AddWidget(path, "Navi Timer Resets", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ResetNaviTimer")) + .Options(CheckboxOptions().Tooltip( + "Resets the Navi timer on scene change. If you have already talked to her, " + "she will try and talk to you again, instead of needing a save warp or death." + )); + AddWidget(path, "No Skulltula Freeze", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkulltulaFreeze")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = + "This setting is disabled because a randomizer savefile is loaded. Please use the " + "\"Skip Get Item Animation\" option within the randomizer enhancements instead."; + }) + .Options(CheckboxOptions().Tooltip( + "Stops the game from freezing the player when picking up Gold Skulltula Tokens. Does not" + "apply in randomizer savefiles." + )); + AddWidget(path, "Ask to Equip New Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AskToEquip")) + .Options(CheckboxOptions().Tooltip( + "Adds a prompt to equip newly-obtained Swords, Shields, and Tunics." + )); + AddWidget(path, "Link as Default File Name", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LinkDefaultName")) + .Options(CheckboxOptions().Tooltip( + "Allows you to have \"Link\" as a premade file name." + )); + AddWidget(path, "Quit Fishing At Door", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) + .Options(CheckboxOptions().Tooltip( + "Fisherman asks if you want to quit at the door if you try to leave the Fishing Pond " + "while still holding the Fishing Rod." + )); + AddWidget(path, "Time Travel with Song of Time", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeTravel")) + .Options(ComboboxOptions() + .ComboMap(timeTravelOptions) + .DefaultIndex(0) + .Tooltip( + "Allows Link to freely change age by playing the Song of Time.\n" + "Time Blocks can still be used properly.\n\n" + "Requirements:\n" + " - Obtained the Ocarina of Time (depends on selection)\n" + " - Obtained the Song of Time\n" + " - Obtained the Master Sword\n" + " - Not within range of a Time Block\n" + " - Not within range of Ocarina Playing spots." + ) + ); + AddWidget(path, "Pause Warp", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PauseWarp")) + .Options(CheckboxOptions().Tooltip( + "Selection of warp song in pause menu initiates a warp. Disables song playback." + )); + AddWidget(path, "Skip Scarecrow's Song", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantScarecrow")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SKIP_SCARECROWS_SONG); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized " + "save file with the option \"Skip Scarecrow Song\" is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first." + )); + AddWidget(path, "Time of Day", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Nighttime GS Always Spawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NightGSAlwaysSpawn")) + .Options(CheckboxOptions().Tooltip( + "Nighttime Skulltulas will spawn during both day and night." + )); + AddWidget(path, "Dampe Appears All Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DampeAllNight")) + .Options(CheckboxOptions().Tooltip( + "Makes Dampe appear anytime during the night, not just his usual working hours." + )); + AddWidget(path, "Exit Market at Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MarketSneak")) + .Options(CheckboxOptions().Tooltip( + "Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard " + "next to the gate." + )); + AddWidget(path, "Shops and Games Always Open", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OpenAllHours")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_LOCK_OVERWORLD_DOORS).Is(RO_GENERIC_ON); + }) + .Options(CheckboxOptions().Tooltip( + "Shops and Minigames are open both day and night. Requires a scene reload to take effect." + ).DisabledTooltip( + "This is not compatible with the Locked Overworld Doors Randomizer option." + )); + path.column = SECTION_COLUMN_3; + AddWidget(path, "Animations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "King Zora Speed: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("MweepSpeed")) + .Options(FloatSliderOptions() + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Format("%.2fx") + ); + AddWidget(path, "Vine/Ladder Climb Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("Climb Speed")) + .Options(IntSliderOptions() + .Min(0) + .Max(12) + .DefaultValue(0) + .Format("+%d") + ); + AddWidget(path, "Block Pushing Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FasterBlockPush")) + .Options(IntSliderOptions() + .Min(0) + .Max(5) + .DefaultValue(0) + .Format("+%d") + ); + AddWidget(path, "Crawl Speed %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CrawlSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(4) + .DefaultValue(1) + .Format("%dx") + ); + AddWidget(path, "Faster Heavy Block Lift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterHeavyBlockLift")) + .Options(CheckboxOptions().Tooltip( + "Speeds up lifting Silver Rocks and Obelisks." + )); + AddWidget(path, "Fast Ocarina Playback", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastOcarinaPlayback")) + .Options(CheckboxOptions().Tooltip( + "Skip the part where the Ocarina Playback is called when you play a song." + )); + AddWidget(path, "Skip Magic Arrow Equip Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipArrowAnimation")); + AddWidget(path, "Faster Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastFarores")) + .Options(CheckboxOptions().Tooltip("Greatly decreases cast time of Farore's Wind magic spell.")); + AddWidget(path, "Fast Chests", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastChests")) + .Options(CheckboxOptions().Tooltip( + "Makes Link always kick the chest to open it, instead of doing the longer " + "chest opening animation for major items." + )); + AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) + .Options(CheckboxOptions().Tooltip( + "Skips Link's taking breath animation after coming up from water. " + "This setting does not interfere with getting items from underwater." + )); + AddWidget(path, "Play Zelda's Lullaby to Open Sleeping Waterfall", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded."; + }) + .Options(ComboboxOptions() + .ComboMap(sleepingWaterfallOptions) + .DefaultIndex(WATERFALL_ALWAYS) + .Tooltip( + "Always: Link must always play Zelda's Lullaby to open the waterfall entrance to Zora's Domain.\n" + "Once: Link only needs to play Zelda's Lullaby once to open the waterfall; after that, it stays " + "open permanently.\n" + "Never: Link never needs to play Zelda's Lullaby to open the waterfall. He only needs to have " + "learned it and have an Ocarina." + ) + ); + + path.sidebarName = "Graphics"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Mods", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Use Alternate Assets", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AltAssets")) + .Options(CheckboxOptions().Tooltip( + "Toggle between standard assets and alternate assets. Usually mods will indicate if " + "this setting has to be used or not." + )); + AddWidget(path, "Disable Bomb Billboarding", WIDGET_CVAR_CHECKBOX) + .CVar("DisableBombBillboarding") + .Options(CheckboxOptions().Tooltip( + "Disables bombs always rotating to face the camera. To be used in conjunction with mods that want to " + "replace bombs with 3D objects." + )); + AddWidget(path, "Disable Grotto Fixed Rotation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableGrottoRotation")) + .Options(CheckboxOptions().Tooltip( + "Disables Grottos rotating with the Camera. To be used in conjuction with mods that want to " + "replace grottos with 3D objects." + )); + + + AddWidget(path, "UI", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Minimal UI", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MinimalUI")) + .Options(CheckboxOptions().Tooltip( + "Hides most of the UI when not needed.\n" + "NOTE: Doesn't activate until scene transition." + )); + AddWidget(path, "Disable Hot/Underwater Warning Text", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableTunicWarningText")) + .Options(CheckboxOptions().Tooltip( + "Disables warning text when you don't have on the Goron/Zora Tunic " + "in Hot/Underwater conditions." + )); + AddWidget(path, "Remember Minimap State Between Areas", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberMapToggleState")) + .Options(CheckboxOptions().Tooltip( + "Preverse the minimap visibility state when going between areas rather than default it to \"on\" " + "when going through loading zones." + )); + AddWidget(path, "Visual Stone of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("VisualAgony")) + .Options(CheckboxOptions().Tooltip( + "Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble." + )); + AddWidget(path, "Disable HUD Heart Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoHUDHeartAnimation")) + .Options(CheckboxOptions().Tooltip( + "Disables the Beating Animation of the Hearts on the HUD." + )); + AddWidget(path, "Glitch line-up tick", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DrawLineupTick")) + .Options(CheckboxOptions().Tooltip( + "Displays a tick in the top center of the screen to help with glitch line-ups in SoH, since traditional " + "UI based line-ups do not work outside of 4:3" + )); + AddWidget(path, "Disable Black Bar Letterboxes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableBlackBars")) + .Options(CheckboxOptions().Tooltip( + "Disables Black Bar Letterboxes during cutscenes and Z-Targeting. NOTE: there may be minor visual " + "glitches that were covered up by the black bars. Please disable this setting before reporting a bug." + )); + AddWidget(path, "Dynamic Wallet Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DynamicWalletIcon")) + .Options(CheckboxOptions().Tooltip( + "Changes the Rupee in the Wallet icon to match the wallet size you currently have." + )); + AddWidget(path, "Always Show Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip( + "Always shows dungeon entrance icons on the Minimap." + )); + AddWidget(path, "More Info in File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FileSelectMoreInfo")) + .Options(CheckboxOptions().Tooltip( + "Shows what items you have collected in the File Select screen, like in N64 Randomizer." + )); + AddWidget(path, "Better Ammo Rendering in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterAmmoRendering")) + .Options(CheckboxOptions().Tooltip( + "Ammo counts in the pause menu will work correctly regardless of the position of items in the Inventory." + )); + + AddWidget(path, "Models", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Invisible Bunny Hood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HideBunnyHood")) + .Options(CheckboxOptions().Tooltip( + "Turns Bunny Hood Invisible while still maintaining its effects." + )); + AddWidget(path, "Animated Link in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PauseMenuAnimatedLink")) + .Options(CheckboxOptions().Tooltip( + "Turns the Static Image of Link in the Pause Menu's Equipment Subsceen " + "into a model cycling through his idle animations." + )); + AddWidget(path, "Show Age-Dependent Equipment", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EquipmentAlwaysVisible")) + .Callback([](WidgetInfo& info) { + UpdatePatchHand(); + }) + .Options(CheckboxOptions().Tooltip( + "Makes all equipment visible, regardless of Age." + )); + AddWidget(path, "Scale Adult Equipment as Child", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Scales all of the Adult Equipment, as well as moving some a bit, to fit on Child Link better. May " + "not work properly with some mods." + )); + AddWidget(path, "Enable 3D Dropped Items/Projectiles", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NewDrops")) + .Options(CheckboxOptions().Tooltip( + "Replaces most 2D items and projectiles on the overworld with their equivalent 3D models." + )); + AddWidget(path, "Show Gauntlets in First Person", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FirstPersonGauntlets")) + .Options(CheckboxOptions().Tooltip( + "Renders Guantlets when using the Bow and Hookshot like in OoT3D." + )); + AddWidget(path, "Show Chains on Both Sides of Locked Doors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ShowDoorLocksOnBothSides")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Textures", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Chest Size & Texture Matches Contents", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { + CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); + } + }) + .Options(ComboboxOptions() + .ComboMap(chestStyleMatchesContentsOptions) + .DefaultIndex(CSMC_DISABLED) + .Tooltip( + "Chest sizes and textures are changed to help identify the item inside.\n" + " - Major items: Large gold chests\n" + " - Lesser items: Large brown chests\n" + " - Junk items: Small brown chests\n" + " - Small keys: Small silver chests\n" + " - Boss keys: Vanilla size and texture\n" + " - Skulltula Tokens: Small skulltula chest\n" + "\n" + "NOTE: Textures will not apply if you are using a mod pack with a custom chest model." + ) + ); + AddWidget(path, "Chests of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchesContents"), CSMC_DISABLED); + }) + .Options(CheckboxOptions().Tooltip( + "Only change the size/texture of chests if you have the Stone of Agony." + )); + AddWidget(path, "Color Temple of Time's Medallions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ToTMedallionsColors")) + .Callback([](WidgetInfo& info) { + PatchToTMedallions(); + }) + .Options(CheckboxOptions().Tooltip( + "When Medallions are collected, the Medallion imprints around the Master Sword Pedestal in the Temple " + "of Time will become colored-in." + )); + AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix")) + .Callback([](WidgetInfo& info) { + if (gPlayState != NULL) { + UpdateDirtPathFixState(gPlayState->sceneNum); + } + }) + .Options(ComboboxOptions() + .ComboMap(zFightingOptions) + .DefaultIndex(ZFIGHT_FIX_DISABLED) + .Tooltip( + "Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" + "Consistent: Certain paths vanish the same way in all resolutions\n" + "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" + "This might affect other decal effects\n" + ) + ); + AddWidget(path, "Text Spacing: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpacing")) + .Options(IntSliderOptions() + .Min(4) + .Max(6) + .DefaultValue(6) + .Tooltip( + "Space between text characters (useful for HD font textures)." + ) + ); + + AddWidget(path, "Rendering", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Disable LOD", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableLOD")) + .Options(CheckboxOptions().Tooltip( + "Turns off the Level of Detail setting, making models use their Higher-Poly variants at any distance." + )); + AddWidget(path, "Increase Actor Draw Distance: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("DisableDrawDistance")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { + CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); + } + }) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Increases the range in which Actors/Objects are drawn." + ) + ); + AddWidget(path, "Kokiri Draw Distance", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableKokiriDrawDistance")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1; + }) + .Options(CheckboxOptions().Tooltip( + "The Kokiri are mystical beings that fade into view when approached. Enabling this will remove their " + "draw distance." + )); + AddWidget(path, "Widescreen Actor Culling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("WidescreenActorCulling")) + .Options(CheckboxOptions().Tooltip( + "Adjusts the Horizontal Culling Plane to account for Widescreen Resolutions." + )); + AddWidget(path, "Cull Glitch Useful Actors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1; + info.options->disabledTooltip = "Requires Actor Draw Distance to be increased or Widscreen Actor Culling to be enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Exclude Actors that are useful for Glitches from the extended culling ranges. Some actors may still draw " + "in the extended ranges, but will not \"update\" so that certain glitches that leverage the original " + "culling requirements will still work.\n\nThe following actors are excluded:\n" + " - White clothed Gerudos\n" + " - King Zora\n" + " - Gossip Stones\n" + " - Boulders\n" + " - Blue Warps\n" + " - Darunia\n" + " - Gold SKulltulas\n" + )); + + // TODO: Find a better home for these. + path.column = SECTION_COLUMN_3; + AddWidget(path, "Misc.", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "N64 Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_LOW_RES_MODE) + .Options(CheckboxOptions().Tooltip( + "Sets the aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution." + )); + AddWidget(path, "Remove Spin Attack Darkness", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RemoveSpinAttackDarkness")) + .Options(CheckboxOptions().Tooltip( + "Remove the Darkness that appears when charging a Spin Attack" + )); + + path.sidebarName = "Items"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Controls", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Equip Items on Dpad", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DpadEquips")) + .Options(CheckboxOptions().Tooltip( + "Equip items and equipment on the D-Pad. If used with \"D-Pad on Pause Screen\", you must " + "hold C-Up to equip instead of navgiate." + )); + AddWidget(path, "Instant Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantPutaway")) + .Options(CheckboxOptions().Tooltip( + "Allow Link to put items away without having to wait around." + )); + AddWidget(path, "Instant Boomerang Recall", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastBoomerang")) + .Options(CheckboxOptions().Tooltip( + "Instantly return the boomerang to Link by pressing its item button while " + "it's in the air." + )); + AddWidget(path, "Prevent Dropped Ocarina Inputs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DpadNoDropOcarinaInput")) + .Options(CheckboxOptions().Tooltip( + "Prevent dropping inputs when playing the Ocarina too quickly." + )); + AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MMBunnyHood")) + .Options(CheckboxOptions().Tooltip( + "Wearing the Bunny Hood grants a speed increase link in Majora's Mask. " + "The longer jump option is not accounted for in Randomizer logic.\n\n" + "Also disables NPC's reactions to wearing the Bunny Hood." + )); + AddWidget(path, "Masks Equippable as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AdultMasks")) + .Options(CheckboxOptions().Tooltip( + "Allows masks to be equipped normally from the pause menu as adult." + )); + AddWidget(path, "Persistent Masks", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PersistentMasks")) + .Options(CheckboxOptions().Tooltip( + "Stops masks from automatically unequipping on certain situations:\n" + "- When entering a new scene\n" + "- When not in any C button or the D-Pad\n" + "- When saving and quitting\n" + "- When dying\n" + "- When traveling thru time (if \"Masks Equippable as Adult\" is activated)." + )); + AddWidget(path, "Mask Select in Inventory", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MaskSelect")) + .Options(CheckboxOptions().Tooltip( + "After completing the mask trading sub-quest, press A and any direction on the mask " + "slog to change masks" + )); + AddWidget(path, "Item Count Messages", WIDGET_SEPARATOR_TEXT); + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + bool allItemCountsChecked = false; + AddWidget(path, "All", WIDGET_CHECKBOX) + .ValuePointer(&allItemCountsChecked) + .PreFunc([](WidgetInfo& info) { + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + *std::get(info.valuePointer) = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [](const char* cvar) { return CVarGetInteger(cvar, 0); }); + }) + .Callback([](WidgetInfo& info) { + int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + for (int i = 0; i < numOptions; i++) { + AddWidget(path, itemCountMessageOptions[i], WIDGET_CVAR_CHECKBOX) + .CVar(itemCountMessageCVars[i]); + } + path.column = SECTION_COLUMN_2; + AddWidget(path, "Equipment", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Deku Nuts Explode Bombs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NutsExplodeBombs")) + .Options(CheckboxOptions().Tooltip( + "Make Deku Nuts explode Bombs, similar to how they interact with Bombchus. " + "This does not affect Bombflowers." + )); + AddWidget(path, "Equip Multiple Arrows at Once", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SeparateArrows")) + .Options(CheckboxOptions().Tooltip( + "Allow the Bow and Magic Arrows to be equipped at the same time on different slots. " + "NOTE: This will disable the behavior of the 'Equip Dupe' glitch." + )); + AddWidget(path, "Bow and Child/Slingshot as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowSlingshotAmmoFix")) + .Options(CheckboxOptions().Tooltip( + "Allows Child to use a Bow with Arrows.\n" + "Allows Adult to use a Slingshot with Seeds.\n\n" + "Requires glitches or the 'Timeless Equipment' cheat to equip." + )); + AddWidget(path, "Better Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterFarore")) + .Options(CheckboxOptions().Tooltip( + "Helps FW persist between ages, gives Child and Adult separate FW points, and can " + "be used in more places." + )); + AddWidget(path, "Remove Explosive Limit", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RemoveExplosiveLimit")) + .Options(CheckboxOptions().Tooltip( + "Removes the cap of 3 active explosives being deployed at once." + )); + AddWidget(path, "Static Explosion Radius", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("StaticExplosionRadius")) + .Options(CheckboxOptions().Tooltip( + "Explosions are now a static size, like in Majora's Mask and OoT3D. Makes Bombchu " + "hovering much easier." + )); + AddWidget(path, "Prevent Bombchus Forcing Firs-Person", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableFirstPersonChus")) + .Options(CheckboxOptions().Tooltip( + "Prevent Bombchus from forcing the camera into first-person mode when released." + )); + AddWidget(path, "Better Bombchu Shopping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterBombchuShopping")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a randomizer."; + }) + .Options(CheckboxOptions().Tooltip( + "Bombchus do not sell out when bought, and a 10 pack of Bombchus costs 99 rupees " + "instead of 100." + )); + AddWidget(path, "Aiming Reticle for the Bow/Slingshot", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowReticle")) + .Options(CheckboxOptions().Tooltip( + "Aiming with a Bow or Slingshot will display a reticle as with the Hookshot " + "when the projectile is ready to fire." + )); + AddWidget(path, "Aim Boomerang in First-Person Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { + CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); + } + }) + .Options(CheckboxOptions().Tooltip( + "Change aiming for the Boomerang from Third-Person to First-Person to see past Link's head." + )); + AddWidget(path, "Aiming Reticle for Boomerang", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0) != 0; + }) + .Options(CheckboxOptions().Tooltip( + "Aiming with the Boomerang will display a reticle as with the Hookshot." + )); + AddWidget(path, "Allow Strength Equipement to be Toggled", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ToggleStrength")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) { + CVarSetInteger(CVAR_ENHANCEMENT("StrengthDisabled"), 0); + } + }) + .Options(CheckboxOptions().Tooltip( + "Allows Strength to be toggled on and off by pressing A on the Strength Upgrade " + "in the Equipment Subscreen of the Pause Menu. This allows performing some glitches " + "that require the player to not have Strength." + )); + // TODO: See if a Callback could be registered to avoid the need to reload scenes for the next two options. + AddWidget(path, "Blue Fire Arrows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BlueFireArrows")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_BLUE_FIRE_ARROWS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Blue Fire Arrows\" is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Allows Ice Arrows to melt Red Ice. May require a room reload if toggled during gameplay." + )); + AddWidget(path, "Sunlight Arrows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SunlightArrows")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_SUNLIGHT_ARROWS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Sunlight Arrows\" enabled is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Allows Light Arrows to activate Sun Switches. May require a room reload if toggled during gameplay." + )); + + // Difficulty Options + path.sidebarName = "Difficulty"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Shooting Gallery", WIDGET_SEPARATOR_TEXT); + auto shootingGalleryDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Customize Behavior##Shooting", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeShootingGallery")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the shooting gallery behavior")); + AddWidget(path, "Instant Win", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantShootingGalleryWin")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip("Skips the Shooting Gallery minigame")); + AddWidget(path, "No Rupee Randomization", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ConstantAdultGallery")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Forces the rupee order to not be randomized as adult, making it the same as child." + )); + AddWidget(path, "Child Starting Ammunition: %d seeds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoChild")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d seeds") + .Tooltip( + "The ammunition at the start of the Shooting Gallery minigame as Child." + ) + ); + AddWidget(path, "Adult Starting Ammunition: %d arrows", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d arrows") + .Tooltip( + "The ammunition at the start of the Shooting Gallery minigame as Adult." + ) + ); + + AddWidget(path, "Bombchu Bowling", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Bowling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeBombchuBowling")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Bombchu Bowling behavior." + )); + auto bombchuBowlingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; + }; + AddWidget(path, "Remove Small Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Prevents the small Cucco from appearing in the Bombchu Bowling minigame." + )); + AddWidget(path, "Remove Big Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Prevents the big Cucco from appearing in the Bombchu Bowling minigame." + )); + AddWidget(path, "Bombchu Count: %d bombchus", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingAmmo")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(IntSliderOptions() + .Min(3) + .Max(20) + .DefaultValue(10) + .Format("%d bombchus") + .Tooltip("The number of Bombchus available at the start of the Bombchu Bowling minigame.") + ); + + AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFishing")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Fishing behavior" + )); + auto fishingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFishing")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "All fish will be caught instantly." + )); + AddWidget(path, "Guarantee Bite", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GuaranteeFishingBite")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "When a line is stable, guarantee bite. Otherwise use Default logic." + )); + AddWidget(path, "Fish Never Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FishNeverEscape")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Once a hook as been set, Fish will never let go while being reeled in." + )); + AddWidget(path, "Loaches Always Appear", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LoachesAlwaysAppear")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Loaches will always appear in the fishing pond instead of every four visits." + )); + AddWidget(path, "Skip Keep Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipKeepConfirmation")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "The Pond Owner will not ask to confirm if you want to keep a smaller Fish." + )); + AddWidget(path, "Child Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightChild")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions() + .Min(3) + .Max(10) + .DefaultValue(10) + .Format("%d lbs.") + .Tooltip( + "The minimum weight for the unique Fishing Reward as a Child." + ) + ); + AddWidget(path, "Adult Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightAdult")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions() + .Min(6) + .Max(13) + .DefaultValue(13) + .Format("%d lbs.") + .Tooltip( + "The minimum weight for the unique fishing reward as an Adult." + ) + ); + AddWidget(path, "All Fish are Hyrule Loaches", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("AllHyruleLoaches")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions().Tooltip( + "Every fish in the Fishing Pond will always be a Hyrule Loach.\n\n" + "NOTE: This requires reloading the area." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Lost Woods Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeOcarinaGame")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Lost Woods Ocarina Game behavior." + )); + auto ocarinaMemoryGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This options is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantOcarinaGameWin")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Skips the Lost Woods Ocarina Memory Game." + )); + AddWidget(path, "Note Play Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Adjust the speed that the Skull Kids play the notes." + ) + ); + AddWidget(path, "Unlimited Playback Time##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Removes the timer to play back the song." + )); + AddWidget(path, "Number of Starting Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(1) + .Max(8) + .DefaultValue(3) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes the Skull Kids play to start the first round." + ) + ); + int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); + AddWidget(path, "Round One Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(5) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the first round." + ) + ); + AddWidget(path, "Round Two Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(6) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the second round." + ) + ); + AddWidget(path, "Round Three Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(8) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the third round." + ) + ); + + AddWidget(path, "Frogs' Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Frogs' Ocarina Game behavior." + )); + auto frogsOcarinaGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFrogsGameWin")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Skips the Frogs' Ocarina Game." + )); + AddWidget(path, "Unlimited Playback Time##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Removes the timer to play back the song." + )); + AddWidget(path, "Modify note timer: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FrogsModifyFailTime")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off or \"Unlimited Playback Time\" is on"; + }) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Adjusts the time allowed for playback before failing." + ) + ); + + AddWidget(path, "Health", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Permanent Heart Loss", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PermanentHeartLoss")) + .Callback([](WidgetInfo& info) { + UpdatePermanentHeartLossState(); + }) + .Options(CheckboxOptions().Tooltip( + "When you lose 4 quarters of a heart you will permanently lose that Heart Container.\n\n" + "Disabling this after the fact will restore your Heart Containers." + )); + AddWidget(path, "Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("DamageMult")) + .Options(ComboboxOptions() + .ComboMap(allPowers) + .DefaultIndex(0) + .Tooltip( + "Modifies all sources of damage not affected by other sliders\n" + "2x: Can survive all common attacks from the start of the game\n" + "4x: Dies in 1 hit to any substantial attack from the start of the game\n" + "8x: Can only survive trivial damage from the start of the game\n" + "16x: Can survive all common attacks with max health without double defense\n" + "32x: Can survive all common attacks with max health and double defense\n" + "64x: Can survive trivial damage with max health without double defense\n" + "128x: Can survive trivial damage with max health and double defense\n" + "256x: Cannot survive damage" + ) + ); + AddWidget(path, "Fall Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("FallDamageMult")) + .Options(ComboboxOptions() + .ComboMap(subPowers) + .Tooltip( + "Modifies all fall damage\n" + "2x: Can survive all fall damage from the start of the game\n" + "4x: Can only survive short fall damage from the start of the game\n" + "8x: Cannot survive any fall damage from the start of the game\n" + "16x: Can survive all fall damage with max health without double defense\n" + "32x: Can survive all fall damage with max health and double defense\n" + "64x: Can survive short fall damage with double defense\n" + "128x: Cannot survive fall damage" + ) + ); + AddWidget(path, "Void Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("VoidDamageMult")) + .Options(ComboboxOptions() + .ComboMap(subSubPowers) + .DefaultIndex(0) + .Tooltip( + "Modifies damage taken after falling into a void\n" + "2x: Can survive void damage from the start of the game\n" + "4x: Cannot survive void damage from the start of the game\n" + "8x: Can survive void damage twice with max health without double defense\n" + "16x: Can survive void damage with max health without double defense\n" + "32x: Can survive void damage with max health and double defense\n" + "64x: Cannot survive void damage" + ) + ); + AddWidget(path, "Bonk Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BonkDamageMult")) + .Options(ComboboxOptions() + .ComboMap(bonkDamageValues) + .DefaultIndex(BONK_DAMAGE_NONE) + .Tooltip("Modifies Damage taken after Bonking.") + ); + AddWidget(path, "Spawn with Full Health", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FullHealthSpawn")) + .Options(CheckboxOptions().Tooltip( + "Respawn with Full Health instead of 3 hearts." + )); + AddWidget(path, "No Heart Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoHeartDrops")) + .Options(CheckboxOptions().Tooltip( + "Disables Heart Drops, but not Heart Placements, like from a Deku Scrub running off.\n" + "This simulates Hero Mode from other games in the series." + )); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Drops", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "No Random Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoRandomDrops")) + .Options(CheckboxOptions().Tooltip( + "Disables Random Drops, except from the Goron Pot, Dampe, and Bosses." + )); + AddWidget(path, "Enable Bombchu Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnableBombchuDrops")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_ENABLE_BOMBCHU_DROPS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Enable Bombchu Drops\" is loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Bombchus will sometimes drop in place of Bombs." + )); + AddWidget(path, "Trees Drop Sticks", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TreesDropSticks")) + .Options(CheckboxOptions().Tooltip( + "Bonking into Trees will have a chance to drop up to 3 Sticks. Must have obtained sticks previously." + )); + + AddWidget(path, "Miscellaneous", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Delete File on Death", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DeleteFileOnDeath")) + .Options(CheckboxOptions().Tooltip( + "Dying will delete your file.\n\n" + ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE + "\nTHIS IS NOT REVERSABLE\nUSE AT YOUR OWN RISK!" + )); + AddWidget(path, "Hyper Bosses", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperBosses")) + .Callback([](WidgetInfo& info) { + UpdateHyperBossesState(); + }) + .Options(CheckboxOptions().Tooltip( + "All Major Bosses move and act twice as fast." + )); + AddWidget(path, "Hyper Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperEnemies")) + .Callback([](WidgetInfo& info) { + UpdateHyperEnemiesState(); + }) + .Options(CheckboxOptions().Tooltip( + "All Regular Enemies and Mini-Bosses move and act twice as fast." + )); + AddWidget(path, "Always Win Goron Pot", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GoronPot")) + .Options(CheckboxOptions().Tooltip( + "Always get the Heart Piece/Purple Rupee from the Spinning Goron Pot." + )); + AddWidget(path, "Always Win Dampe Digging Game", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DampeWin")) + .Options(CheckboxOptions().Tooltip( + "Always win the Heart Piece/Purple Rupee on the first dig in Dampe's Grave Digging game. " + "In a Randomizer file, this is always enabled." + )); + AddWidget(path, "All Dogs are Richard", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AllDogsRichard")) + .Options(CheckboxOptions().Tooltip( + "All dogs can be traded in and will count as Richard." + )); + AddWidget(path, "Cuccos Stay Put Multiplier: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CuccoStayDurationMult")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider." + ) + ); + AddWidget(path, "Leever Spawn Rate: %d seconds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("LeeverSpawnRate")) + .Options(IntSliderOptions() + .Min(0) + .Max(10) + .DefaultValue(0) + .Format("%d seconds") + .Tooltip( + "The time between groups of Leevers spawning." + ) + ); + + path.sidebarName = "Extra Modes"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Mirrored World", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("MirroredWorldMode")) + .Callback([](WidgetInfo& info) { + if (gPlayState != NULL) { + UpdateMirrorModeState(gPlayState->sceneNum); + } + }) + .Options(ComboboxOptions() + .DefaultIndex(MIRRORED_WORLD_OFF) + .ComboMap(mirroredWorldModes) + .Tooltip( + "Mirrors the world horizontally\n\n" + " - Always: Always mirror the world\n" + " - Random: Randomly decide to mirror the world on each scene change\n" + " - Random (Seeded): Scenes are mirrored based on the current randomizer seed/file\n" + " - Dungeons: Mirror the world in Dungeons\n" + " - Dungeons (Vanilla): Mirror the world in vanilla Dungeons\n" + " - Dungeons (MQ): Mirror the world in MQ Dungeons\n" + " - Dungeons Random: Randomly decide to mirror the world in Dungeons\n" + " - Dungeons Random (Seeded): Dungeons are mirrored based on the current randomizer seed/file\n" + ) + ); + AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) + .Options(CheckboxOptions().Tooltip( + "Enemies and Bosses spawn with random sizes." + )); + AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Scales normal enemies Health with their randomized size. *This will NOT affect bosses*" + )); + AddWidget(path, "Ivan the Fairy (Coop Mode)", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("IvanCoopModeEnabled")) + .Options(CheckboxOptions().Tooltip( + "Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and press the C-Buttons to " + "use items and mess with Player 1!" + )); + AddWidget(path, "Rupee Dash Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RupeeDash")) + .Options(CheckboxOptions().Tooltip( + "Rupees reduce over time, Link suffers damage when the count hits 0." + )); + AddWidget(path, "Rupee Dash Interval %d seconds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("RupeeDashInterval")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0) == 0; + }) + .Options(IntSliderOptions() + .Min(1) + .Max(10) + .DefaultValue(5) + .Format("%d seconds") + .Tooltip( + "Interval between Rupee reduction in Rupee Dash Mode." + ) + ); + AddWidget(path, "Shadow Tag Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ShadowTag")) + .Options(CheckboxOptions().Tooltip( + "A Wallmaster follows Link everywhere, don't get caught!" + )); + AddWidget(path, "Hurt Container Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HurtContainer")) + .Callback([](WidgetInfo& info) { + UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); + }) + .Options(CheckboxOptions().Tooltip( + "Changes Heart Piece and Heart Container functionality.\n\n" + " - Each Heart Container or full Heart Piece reduces Link's Hearts by 1.\n" + " - Can be enabled retroactively after a File has already started." + )); + AddWidget(path, "Additional Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Enabled")) + .Options(CheckboxOptions().Tooltip( + "Enables additional Trap variants." + )); + AddWidget(path, "Trap Options", WIDGET_SEPARATOR_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 1 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Freeze Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ice")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Burn Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Burn")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Shock Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Shock")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 2 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Knockback Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Knockback")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Speed Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Speed")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Bomb Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Bomb")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 3 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Void Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Void")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Ammo Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ammo")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Death Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Kill")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Teleport Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Teleport")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + path.column = SECTION_COLUMN_2; + AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemies")) + .Callback([](WidgetInfo& info) { + GetSelectedEnemies(); + }) + .Options(ComboboxOptions() + .DefaultIndex(ENEMY_RANDOMIZER_OFF) + .ComboMap(enemyRandomizerModes) + .Tooltip( + "Replaces fixed enemies throughout the game with a random enemy. Bosses, mini-bosses and a few specific regular enemies are excluded.\n" + "Enemies that need more than Deku Nuts + either Deku Sticks or a sword to kill are excluded from spawning in \"clear enemy\" rooms.\n\n" + "- Random: Enemies are randomized every time you load a room\n" + "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file\n" + ) + ); + AddWidget(path, "Enemy List", WIDGET_SEPARATOR_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + }); + AddWidget(path, "Select All Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + }); + AddWidget(path, "Enemy List", WIDGET_SEPARATOR); + for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { + AddWidget(path, enemyNameList[i], WIDGET_CVAR_CHECKBOX) + .CVar(enemyCVarList[i]) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0) == 1; + info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled."; + }) + .Callback([](WidgetInfo& info) { + GetSelectedEnemies(); + }); + } + + // Cheats + path.sidebarName = "Cheats"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Inventory", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SuperTunic")) + .Options(CheckboxOptions().Tooltip( + "Makes every tunic have the effects of every other tunic." + )); + AddWidget(path, "Easy ISG", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyISG")) + .Options(CheckboxOptions().Tooltip( + "Passive Infinite Sword Glitch\n" + "It makes your sword's swing effect and hitbox stay active indefinitely." + )); + AddWidget(path, "Easy QPA", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyQPA")) + .Options(CheckboxOptions().Tooltip( + "Gives you the glitched damage value of the quick put away glitch." + )); + AddWidget(path, "Timeless Equipment", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("TimelessEquipment")) + .Options(CheckboxOptions().Tooltip( + "Allows any item to be equipped, regardless of age.\n" + "Also allows Child to use Adult strength upgrades." + )); + AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("Unrestricted Items")) + .Options(CheckboxOptions().Tooltip( + "Allows you to use any item at any location" + )); + AddWidget(path, "Fireproof Deku Shield", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FireproofDekuShield")) + .Options(CheckboxOptions().Tooltip( + "Prevents the Deku Shield from burning on contact with fire." + )); + AddWidget(path, "Shield with Two-Handed Weapons", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("ShieldTwoHanded")) + .Options(CheckboxOptions().Tooltip( + "This allows you to put up for shield with any two-handed weapon in hand except for Deku Sticks." + )); + AddWidget(path, "Deku Sticks:", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_CHEAT("DekuStick")) + .Options(ComboboxOptions().ComboMap( + dekuStickCheat + ).DefaultIndex(DEKU_STICK_NORMAL)); + AddWidget(path, "Bomb Timer Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("BombTimerMultiplier")) + .Options(FloatSliderOptions() + .Format("%.2f") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f)); + AddWidget(path, "Hookshot Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("HookshotEverything")) + .Options(CheckboxOptions().Tooltip( + "Makes every surface in the game hookshot-able." + )); + AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("HookshotReachMultiplier")) + .Options(FloatSliderOptions() + .Format("%.2f") + .Min(1.0f) + .Max(5.0f)); + AddWidget(path, "Change Age", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.")) + .Callback([](WidgetInfo& info){ + SwitchAge(); + }); + AddWidget(path, "Clear Cutscene Pointer", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + GameInteractor::RawAction::ClearCutscenePointer(); + }) + .Options(ButtonOptions().Tooltip( + "Clears the cutscene pointer to a value safe for wrong warps." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Infinite...", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Money", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMoney")); + AddWidget(path, "Health", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteHealth")); + AddWidget(path, "Ammo", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteAmmo")); + AddWidget(path, "Magic", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMagic")); + AddWidget(path, "Nayru's Love", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteNayru")); + AddWidget(path, "Epona Boost", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteEponaBoost")); + + AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); + AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " WARNING!!!! " ICON_FA_EXCLAMATION_TRIANGLE, WIDGET_TEXT) + .Options(WidgetOptions().Color(Colors::Orange)); + AddWidget(path, + "These are NOT like emulator states. They do not save your game progress " + "and they WILL break across transitions and load zones (like doors). " + "Support for related issues will not be provided.", WIDGET_TEXT + ); + AddWidget(path, "I promise I have read the warning", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SaveStatePromise")) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + AddWidget(path, "I understand, enable save states", WIDGET_CVAR_CHECKBOX) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 0; + }) + .CVar(CVAR_CHEAT("SaveStatesEnabled")) + .Options(CheckboxOptions().Tooltip( + "F5 to save, F6 to change slots, F7 to load" + )); + + AddWidget(path, "Behavior", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "No Clip", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoClip")) + .Options(CheckboxOptions().Tooltip("Allows you to walk through walls.")); + AddWidget(path, "Climb Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("ClimbEverything")) + .Options(CheckboxOptions().Tooltip("Makes every surface in the game climbable.")); + AddWidget(path, "Moon Jump on L", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("MoonJumpOnL")) + .Options(CheckboxOptions().Tooltip("Holding L makes you float into the air.")); + AddWidget(path, "New Easy Frame Advancing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyFrameAdvance")) + .Options(CheckboxOptions().Tooltip( + "Continue holding START button when unpausing to only advance a single frame and then re-pause." + )); + AddWidget(path, "Drops Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("DropsDontDie")) + .Options(CheckboxOptions().Tooltip( + "Drops from enemies, grass, etc. don't disappear after a set amount of time." + )); + AddWidget(path, "Fish Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoFishDespawn")) + .Options(CheckboxOptions().Tooltip( + "Prevents fish from automatically despawning after a while when dropped." + )); + AddWidget(path, "Bugs Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoBugsDespawn")) + .Options(CheckboxOptions().Tooltip( + "Prevents bugs from automatically despawning after a while when dropped." + )); + AddWidget(path, "Freeze Time", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FreezeTime")) + .Options(CheckboxOptions().Tooltip("Freezes the time of day")); + AddWidget(path, "Time Sync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("TimeSync")) + .Options(CheckboxOptions().Tooltip("Syncs the in-game time with the real world time.")); + AddWidget(path, "No ReDead/Gibdo Freeze", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoRedeadFreeze")) + .Options(CheckboxOptions().Tooltip( + "Prevents ReDeads and Gibdos from being able to freeze you with their scream." + )); + AddWidget(path, "Keese/Guay Don't Target You", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoKeeseGuayTarget")) + .Options(CheckboxOptions().Tooltip( + "Keese and Guay no longer target you and simply ignore you as if you were wearing the " + "Skull Mask." + )); + path.column = SECTION_COLUMN_3; + AddWidget(path, "Beta Quest", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Enable Beta Quest", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EnableBetaQuest")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !isBetaQuestEnabled; + }) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0) == 0) { + CVarClear(CVAR_CHEAT("BetaQuestWorld")); + } else { + CVarSetInteger(CVAR_CHEAT("BetaQuestWorld"), 0); + } + std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(CheckboxOptions().Tooltip( + "Turns on OoT Beta Quest. *WARNING* This will reset your game." + )); + AddWidget(path, "Beta Quest World: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_CHEAT("BetaQuestWorld")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = info.isHidden = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0) == 0; + }) + .Callback([](WidgetInfo& info) { + std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(IntSliderOptions() + .DefaultValue(0) + .Min(0) + .Max(8) + .Tooltip( + "Set the Beta Quest world to explore. *WARNING* Changing this will reset your game.\n" + "Ctrl+Click to type in a value." + )); + + // Cosmetics Editor + path.sidebarName = "Cosmetics Editor"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + path.column = SECTION_COLUMN_1; + AddWidget(path, "Popout Cosmetics Editor Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CosmeticsEditor")) + .WindowName("Cosmetics Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Cosmetics Editor Window.")); + + + // Audio Editor + path.sidebarName = "Audio Editor"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Popout Audio Editor Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("AudioEditor")) + .WindowName("Audio Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Audio Editor Window.")); + + + // Gameplay Stats + path.sidebarName = "Gameplay Stats"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + AddWidget(path, "Popout Gameplay Stats Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("GameplayStats")) + .WindowName("Gameplay Stats") + .Options(WindowButtonOptions().Tooltip("Enables the separate Gameplay Stats Window.")); + + + // Time Splits + path.sidebarName = "Time Splits"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Popout Time Splits Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("TimeSplits")) + .WindowName("Time Splits") + .Options(WindowButtonOptions().Tooltip("Enables the separate Time Splits Window.")); + + + // Timers + path.sidebarName = "Timers"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Toggle Timers Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("TimeDisplayEnabled")) + .WindowName("Additional Timers") + .Options(WindowButtonOptions().Tooltip("Enables the separate Additional Timers Window.")); + AddWidget(path, "Font Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("TimeDisplay.FontScale")) + .Callback([](WidgetInfo& info) { + TimeDisplayInitSettings(); + }) + .Options(FloatSliderOptions() + .Min(1.0f) + .Max(5.0f) + .DefaultValue(1.0f) + .Format("%.2fx") + ); + AddWidget(path, "Hide Background", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG")) + .Callback([](WidgetInfo& info) { + TimeDisplayInitSettings(); + }); + for (auto& timer : timeDisplayList) { + AddWidget(path, timer.timeLabel, WIDGET_CVAR_CHECKBOX) + .CVar(timer.timeEnable) + .Callback([](WidgetInfo& info) { + TimeDisplayUpdateDisplayOptions(); + }); + } +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuNetwork.cpp b/soh/soh/SohGui/SohMenuNetwork.cpp new file mode 100644 index 000000000..29511a9e9 --- /dev/null +++ b/soh/soh/SohGui/SohMenuNetwork.cpp @@ -0,0 +1,148 @@ +#ifdef ENABLE_REMOTE_CONTROL +#include "SohMenu.h" +#include +#include +#include "SohGui.hpp" +#include +#include + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuNetwork() { + // Add Network Menu + AddMenuEntry("Network", CVAR_SETTING("Menu.NetworkSidebarSection")); + + // Sail + WidgetPath path = { "Network", "Sail", SECTION_COLUMN_1 }; + AddSidebarEntry("Network", path.sidebarName, 3); + + AddWidget(path, "Sail is a networking protocol designed to facilitate remote " + "control of the Ship of Harkinian client. It is intended to " + "be utilized alongside a Sail server, for which we provide a " + "few straightforward implementations on our GitHub. The current " + "implementations available allow integration with Twitch chat " + "and SAMMI Bot, feel free to contribute your own!\n" + "\n" + "Click this button to copy the link to the Sail Github " + "page to your clipboard.", WIDGET_TEXT); + AddWidget(path, ICON_FA_CLIPBOARD "##Sail", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + ImGui::SetClipboardText("https://github.com/HarbourMasters/sail"); + Notification::Emit({ + .message = "Copied to clipboard", + }); + }) + .Options(ButtonOptions() + .Tooltip("https://github.com/HarbourMasters/sail") + ); + AddWidget(path, "Host & Port", WIDGET_CUSTOM) + .CustomFunction([](WidgetInfo& info) { + ImGui::BeginDisabled(Sail::Instance->isEnabled); + ImGui::Text("%s", info.name.c_str()); + CVarInputString("##HostSail", CVAR_REMOTE_SAIL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + CVarInputInt("##PortSail", CVAR_REMOTE_SAIL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + ImGui::EndDisabled(); + }); + AddWidget(path, "Enable##Sail", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { + std::string host = CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"); + uint16_t port = CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384); + info.options->disabled = !(!SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535); + if (Sail::Instance->isEnabled) { + info.name = "Disable##Sail"; + } else { + info.name = "Enable##Sail"; + } + }) + .Callback([](WidgetInfo& info) { + if (Sail::Instance->isEnabled) { + CVarClear(CVAR_REMOTE_SAIL("Enabled")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Sail::Instance->Disable(); + } else { + CVarSetInteger(CVAR_REMOTE_SAIL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Sail::Instance->Enable(); + } + }); + AddWidget(path, "Connecting...##Sail", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = !Sail::Instance->isEnabled; + if (Sail::Instance->isConnected) { + info.name = "Connected##Sail"; + } else { + info.name = "Connecting...##Sail"; + } + }); + + path.sidebarName = "Crowd Control"; + AddSidebarEntry("Network", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Crowd Control is a platform that allows viewers to interact " + "with a streamer's game in real time.\n" + "\n" + "Click the question mark to copy the link to the Crowd Control " + "website to your clipboard.", WIDGET_TEXT); + AddWidget(path, ICON_FA_CLIPBOARD "##CrowdControl", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + ImGui::SetClipboardText("https://crowdcontrol.live"); + Notification::Emit({ + .message = "Copied to clipboard", + }); + }) + .Options(ButtonOptions() + .Tooltip("https://crowdcontrol.live") + ); + AddWidget(path, "Host & Port", WIDGET_CUSTOM) + .CustomFunction([](WidgetInfo& info) { + ImGui::BeginDisabled(CrowdControl::Instance->isEnabled); + ImGui::Text("%s", info.name.c_str()); + CVarInputString("##HostCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + CVarInputInt("##PortCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + ImGui::EndDisabled(); + }); + AddWidget(path, "Enable##CrowdControl", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { + std::string host = CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"); + uint16_t port = CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384); + info.options->disabled = !(!SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535); + if (CrowdControl::Instance->isEnabled) { + info.name = "Disable##CrowdControl"; + } else { + info.name = "Enable##CrowdControl"; + } + }) + .Callback([](WidgetInfo& info) { + if (CrowdControl::Instance->isEnabled) { + CVarClear(CVAR_REMOTE_CROWD_CONTROL("Enabled")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + CrowdControl::Instance->Disable(); + } else { + CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + CrowdControl::Instance->Enable(); + } + }); + AddWidget(path, "Connecting...##CrowdControl", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = !CrowdControl::Instance->isEnabled; + if (CrowdControl::Instance->isConnected) { + info.name = "Connected##CrowdControl"; + } else { + info.name = "Connecting...##CrowdControl"; + } + }); +} + +} // namespace SohGui +#endif \ No newline at end of file diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp new file mode 100644 index 000000000..e6399baab --- /dev/null +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -0,0 +1,133 @@ +#include "SohMenu.h" +#include + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuRandomizer() { + // Add Randomizer Menu + AddMenuEntry("Randomizer", CVAR_SETTING("Menu.RandomizerSidebarSection")); + + // Seed Settings + WidgetPath path = { "Randomizer", "Seed Settings", SECTION_COLUMN_1 }; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Popout Randomizer Settings Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("RandomizerSettings")) + .WindowName("Randomizer Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window.")); + path.sidebarName = "Enhancements"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Rando-Relevant Navi Hints", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi")) + .Options(CheckboxOptions().Tooltip( + "Replace Navi's overworld quest hints with rando-related gameplay hints." + ).DefaultValue(true)); + AddWidget(path, "Random Rupee Names", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames")) + .Options(CheckboxOptions().Tooltip( + "When obtaining Rupees, randomize what the Rupee is called in the textbox." + ).DefaultValue(true)); + AddWidget(path, "Use Custom Key Models", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels")) + .Options(CheckboxOptions().Tooltip( + "Use Custom graphics for Dungeon Keys, Big and Small, so that they can be easily told apart." + ).DefaultValue(true)); + AddWidget(path, "Compass Colors Match Dungeon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !( + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH) && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_VANILLA) && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) + ); + info.options->disabledTooltip = "This setting is disabled because a savefile is loaded without " + "the compass shuffle settings set to Any Dungeon, Overworld, or Anywhere."; + }) + .Options(CheckboxOptions().Tooltip( + "Matches the color of compasses to the dungeon they belong to. " + "This helps identify compasses from afar and adds a little bit of flair.\n\nThis only " + "applies to seeds with compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"." + ).DefaultValue(true)); + AddWidget(path, "Quest Item Fanfares", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares")) + .Options(CheckboxOptions().Tooltip( + "Play unique fanfares when obtaining quest items (medallions/stones/songs). Note that these " + "fanfares can be longer than usual." + )); + AddWidget(path, "Mysterious Shuffled Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle")) + .Options(CheckboxOptions().Tooltip( + "Displays a \"Mystery Item\" model in place of any freestanding/GS/shop items that were shuffled, " + "and replaces item names for them and scrubs and merchants, regardless of hint settings, " + "so you never know what you're getting." + )); + AddWidget(path, "Simpler Boss Soul Models", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("SimplerBossSoulModels")) + .Options(CheckboxOptions().Tooltip( + "When shuffling boss souls, they'll appear as a simpler model instead of showing the boss' models." + "This might make boss souls more distinguishable from a distance, and can help with performance." + )); + AddWidget(path, "Skip Get Item Animations", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation")) + .Options(ComboboxOptions().ComboMap(skipGetItemAnimationOptions).DefaultIndex(SGIA_JUNK)); + + // Plandomizer + path.sidebarName = "Plandomizer"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Popout Plandomizer Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("PlandomizerEditor")) + .WindowName("Plandomizer Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window.")); + + // Item Tracker + path.sidebarName = "Item Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Item Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Item Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ItemTracker")) + .WindowName("Item Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Item Tracker.").EmbedWindow(false)); + + AddWidget(path, "Item Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Item Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ItemTrackerSettings")) + .WindowName("Item Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Item Tracker Settings Window.")); + + // Entrance Tracker + path.sidebarName = "Entrance Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Entrance Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Entrance Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("EntranceTracker")) + .WindowName("Entrance Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Entrance Tracker.").EmbedWindow(false)); + + AddWidget(path, "Entrance Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Entrance Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("EntranceTrackerSettings")) + .WindowName("Entrance Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Entrance Tracker Settings Window.")); + + // Check Tracker + path.sidebarName = "Check Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Check Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Check Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CheckTracker")) + .WindowName("Check Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Check Tracker.").EmbedWindow(false)); + + AddWidget(path, "Check Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Check Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CheckTrackerSettings")) + .WindowName("Check Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Check Tracker Settings Window.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp new file mode 100644 index 000000000..a9103377b --- /dev/null +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -0,0 +1,373 @@ +#include "SohMenu.h" +#include "soh/Notification/Notification.h" +#include +#include "soh/ResourceManagerHelpers.h" +#include "UIWidgets.hpp" +#include + +extern "C" { +#include "include/z64audio.h" +#include "variables.h" +} + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; +static std::unordered_map languages = {{ LANGUAGE_ENG, "English" }, { LANGUAGE_GER, "German" }, { LANGUAGE_FRA, "French" }}; + +const char* GetGameVersionString(uint32_t index) { + uint32_t gameVersion = ResourceMgr_GetGameVersion(index); + switch (gameVersion) { + case OOT_NTSC_US_10: + return "NTSC-U 1.0"; + case OOT_NTSC_US_11: + return "NTSC-U 1.1"; + case OOT_NTSC_US_12: + return "NTSC-U 1.2"; + case OOT_PAL_10: + return "PAL 1.0"; + case OOT_PAL_11: + return "PAL 1.1"; + case OOT_PAL_GC: + return "PAL GC"; + case OOT_PAL_MQ: + return "PAL MQ"; + case OOT_PAL_GC_DBG1: + case OOT_PAL_GC_DBG2: + return "PAL GC-D"; + case OOT_PAL_GC_MQ_DBG: + return "PAL MQ-D"; + case OOT_IQUE_CN: + return "IQUE CN"; + case OOT_IQUE_TW: + return "IQUE TW"; + default: + return "UNKNOWN"; + } +} + +void SohMenu::AddMenuSettings() { + // Add Settings Menu + AddMenuEntry("Settings", CVAR_SETTING("Menu.SettingsSidebarSection")); + AddSidebarEntry("Settings", "General", 3); + WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; + + // General - Settings + AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Menu.Theme")) + .Options(ComboboxOptions() + .Tooltip("Changes the Theme of the Menu Widgets.") + .ComboMap(menuThemeOptions) + .DefaultIndex(Colors::LightBlue)); +#if not defined(__SWITCH__) and not defined(__WIIU__) + AddWidget(path, "Menu Controller Navigation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_IMGUI_CONTROLLER_NAV) + .Options(CheckboxOptions().Tooltip( + "Allows controller navigation of the 2Ship menu (Settings, Enhancements,...)\nCAUTION: " + "This will disable game inputs while the menu is visible.\n\nD-pad to move between " + "items, A to select, B to move up in scope.")); + AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("CursorVisibility")) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetForceCursorVisibility( + CVarGetInteger(CVAR_SETTING("CursorVisibility"), 0)); + }) + .Options(CheckboxOptions().Tooltip("Makes the cursor always visible, even in full screen.")); +#endif + AddWidget(path, "Search In Sidebar", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Menu.SidebarSearch")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { + mSohMenu->InsertSidebarSearch(); + } else { + mSohMenu->RemoveSidebarSearch(); + } + }) + .Options(CheckboxOptions().Tooltip( + "Displays the Search menu as a sidebar entry in Settings instead of in the header.")); + AddWidget(path, "Search Input Autofocus", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Menu.SearchAutofocus")) + .Options(CheckboxOptions().Tooltip( + "Search input box gets autofocus when visible. Does not affect using other widgets.")); + AddWidget(path, "Alt Assets Tab hotkey", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Mods.AlternateAssetsHotkey")) + .Options( + CheckboxOptions().Tooltip("Allows pressing the Tab key to toggle alternate assets").DefaultValue(true)); + AddWidget(path, "Open App Files Folder", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); + SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); + }) + .Options(ButtonOptions().Tooltip("Opens the folder that contains the save and mods folders, etc.")); + AddWidget(path, "Languages", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Translate Title Screen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("TitleScreenTranslation")); + AddWidget(path, "Menu Language", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Languages")) + .Options(ComboboxOptions().LabelPosition(LabelPosition::Far).ComponentAlignment(ComponentAlignment::Right).ComboMap(languages).DefaultIndex(LANGUAGE_ENG)); + AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); + #if defined(_WIN32) || defined(__APPLE__) + AddWidget(path, "Text to Speech", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("A11yTTS")) + .Options(CheckboxOptions().Tooltip("Enables text to speech for in game dialog")); + #endif + AddWidget(path, "Disable Idle Camera Re-Centering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("A11yDisableIdleCam")) + .Options(CheckboxOptions().Tooltip("Disables the automatic re-centering of the camera when idle.")); + + // General - About + path.column = SECTION_COLUMN_2; + + AddWidget(path, "About", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Ship Of Harkinian", WIDGET_TEXT); + if (gGitCommitTag[0] != 0) { + AddWidget(path, gBuildVersion, WIDGET_TEXT); + } else { + AddWidget(path, ("Branch: " + std::string(gGitBranch)), WIDGET_TEXT); + AddWidget(path, ("Commit: " + std::string(gGitCommitHash)), WIDGET_TEXT); + } + for (uint32_t i = 0; i < ResourceMgr_GetNumGameVersions(); i++) { + AddWidget(path, GetGameVersionString(i), WIDGET_TEXT); + } + + // Audio Settings + path.sidebarName = "Audio"; + path.column = SECTION_COLUMN_1; + AddSidebarEntry("Settings", "Audio", 3); + + AddWidget(path, "Master Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.Master")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(40) + .ShowButtons(true) + .Format("")); + AddWidget(path, "Main Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.MainMusic")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_BGM_MAIN, ((float)CVarGetInteger(CVAR_SETTING("Volume.MainMusic"), 100) / 100.0f)); + }); + AddWidget(path, "Sub Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.SubMusic")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_BGM_SUB, ((float)CVarGetInteger(CVAR_SETTING("Volume.SubMusic"), 100) / 100.0f)); + }); + AddWidget(path, "Fanfare Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.Fanfare")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_FANFARE, ((float)CVarGetInteger(CVAR_SETTING("Volume.Fanfare"), 100) / 100.0f)); + }); + AddWidget(path, "Sound Effects Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.SFX")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_SFX, ((float)CVarGetInteger(CVAR_SETTING("Volume.SFX"), 100) / 100.0f)); + }); + AddWidget(path, "Audio API (Needs reload)", WIDGET_AUDIO_BACKEND); + + // Graphics Settings + static int32_t maxFps; + const char* tooltip = ""; + if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { + maxFps = 360; + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target " + "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; + } else { + maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc."; + } + path.sidebarName = "Graphics"; + AddSidebarEntry("Settings", "Graphics", 3); + AddWidget(path, "Toggle Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Fullscreen")) + .Callback([](WidgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); }) + .Options(CheckboxOptions().Tooltip("Toggles Fullscreen On/Off.")); + AddWidget(path, "Internal Resolution", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_INTERNAL_RESOLUTION) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( + CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); + }) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->disabledMap.at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active && + mSohMenu->disabledMap.at(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_ON); + info.activeDisables.push_back(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON); + } else if (mSohMenu->disabledMap.at(DISABLE_FOR_LOW_RES_MODE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_LOW_RES_MODE_ON); + } + }) + .Options( + FloatSliderOptions() + .Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective " + "form of anti-aliasing.") + .ShowButtons(false) + .IsPercentage() + .Min(0.5f) + .Max(2.0f)); +#ifndef __WIIU__ + AddWidget(path, "Anti-aliasing (MSAA)", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_MSAA_VALUE) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); + }) + .Options( + IntSliderOptions() + .Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of " + "rendered geometry.\n" + "Higher sample count will result in smoother edges on models, but may reduce performance.") + .Min(1) + .Max(8) + .DefaultValue(1)); +#endif + auto fps = CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); + const char* fpsFormat = fps == 20 ? "Original (%d)" : "%d"; + AddWidget(path, "Current FPS", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("InterpolationFPS")) + .Callback([](WidgetInfo& info) { + auto options = std::static_pointer_cast(info.options); + int32_t defaultValue = options->defaultValue; + if (CVarGetInteger(info.cVar, defaultValue) == defaultValue) { + options->format = "Original (%d)"; + } + else { + options->format = "%d"; + } + }) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->disabledMap.at(DISABLE_FOR_MATCH_REFRESH_RATE_ON).active) + info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON); + }) + .Options(IntSliderOptions().Tooltip(tooltip).Min(20).Max(maxFps).DefaultValue(20).Format(fpsFormat)); + AddWidget(path, "Match Refresh Rate", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + if (hz >= 20 && hz <= 360) { + CVarSetInteger(CVAR_SETTING("InterpolationFPS"), hz); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + }) + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; }) + .Options(ButtonOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX) + .CVar("gMatchRefreshRate") + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_DIRECTX).active; }) + .Options(CheckboxOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND); + AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_VSYNC_ENABLED) + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; }) + .Options(CheckboxOptions().Tooltip("Enables Vsync.")); + AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SDL_WINDOWED_FULLSCREEN) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_WINDOWED_FULLSCREEN).active; + }) + .Options(CheckboxOptions().Tooltip("Enables Windowed Fullscreen Mode.")); + AddWidget(path, "Allow multi-windows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENABLE_MULTI_VIEWPORTS) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_MULTI_VIEWPORT).active; }) + .Options(CheckboxOptions().Tooltip( + "Allows multiple windows to be opened at once. Requires a reload to take effect.")); + AddWidget(path, "Texture Filter (Needs reload)", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_TEXTURE_FILTER) + .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap)); + + + // Controls + path.sidebarName = "Controls"; + AddSidebarEntry("Settings", "Controls", 2); + AddWidget(path, "Controller Bindings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ControllerConfiguration")) + .WindowName("Configure Controller") + .Options(WindowButtonOptions().Tooltip("Enables the separate Bindings Window.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Input Viewer", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Input Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("InputViewer")) + .WindowName("Input Viewer") + .Options(WindowButtonOptions().Tooltip("Toggles the Input Viewer.").EmbedWindow(false)); + + AddWidget(path, "Input Viewer Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Input Viewer Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("InputViewerSettings")) + .WindowName("Input Viewer Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Input Viewer Settings Window.")); + + // Notifications + path.sidebarName = "Notifications"; + path.column = SECTION_COLUMN_1; + AddSidebarEntry("Settings", "Notifications", 3); + AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Notifications.Position")) + .Options(ComboboxOptions() + .Tooltip("Which corner of the screen notifications appear in.") + .ComboMap(notificationPosition) + .DefaultIndex(3)); + AddWidget(path, "Duration: %.0f seconds", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.Duration")) + .Options(FloatSliderOptions() + .Tooltip("How long notifications are displayed for.") + .Format("%.1f") + .Step(0.1f) + .Min(3.0f) + .Max(30.0f) + .DefaultValue(10.0f)); + AddWidget(path, "Background Opacity: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.BgOpacity")) + .Options(FloatSliderOptions() + .Tooltip("How opaque the background of notifications is.") + .DefaultValue(0.5f) + .IsPercentage()); + AddWidget(path, "Size %.1f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.Size")) + .Options(FloatSliderOptions() + .Tooltip("How large notifications are.") + .Format("%.1f") + .Step(0.1f) + .Min(1.0f) + .Max(5.0f) + .DefaultValue(1.8f)); + AddWidget(path, "Test Notification", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + Notification::Emit({ + .itemIcon = "__OTR__textures/icon_item_24_static/gQuestIconGoldSkulltulaTex", + .prefix = "This", + .message = "is a", + .suffix = "test.", + }); + }) + .Options(ButtonOptions().Tooltip("Displays a test notification.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohModals.cpp b/soh/soh/SohGui/SohModals.cpp index 642fedf15..c810d52e6 100644 --- a/soh/soh/SohGui/SohModals.cpp +++ b/soh/soh/SohGui/SohModals.cpp @@ -5,6 +5,7 @@ #include #include #include "UIWidgets.hpp" +#include "SohGui.hpp" #include "soh/OTRGlobals.h" #include "z64.h" @@ -29,6 +30,7 @@ void SohModalWindow::Draw() { } void SohModalWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); if (modals.size() > 0) { SohModal curModal = modals.at(0); if (!ImGui::IsPopupOpen(curModal.title_.c_str())) { @@ -36,6 +38,7 @@ void SohModalWindow::DrawElement() { } if (ImGui::BeginPopupModal(curModal.title_.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { ImGui::Text("%s", curModal.message_.c_str()); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(curModal.button1_.c_str())) { if (curModal.button1callback_ != nullptr) { curModal.button1callback_(); @@ -43,8 +46,10 @@ void SohModalWindow::DrawElement() { ImGui::CloseCurrentPopup(); modals.erase(modals.begin()); } - ImGui::SameLine(); + UIWidgets::PopStyleButton(); if (curModal.button2_ != "") { + ImGui::SameLine(); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(curModal.button2_.c_str())) { if (curModal.button2callback_ != nullptr) { curModal.button2callback_(); @@ -52,10 +57,12 @@ void SohModalWindow::DrawElement() { ImGui::CloseCurrentPopup(); modals.erase(modals.begin()); } + UIWidgets::PopStyleButton(); } } ImGui::EndPopup(); } + ImGui::PopFont(); } void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 6d9ca7a65..7fbad6705 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -1,836 +1,1139 @@ -// -// UIWidgets.cpp -// soh -// -// Created by David Chavez on 25.08.22. -// - #include "UIWidgets.hpp" - -#include +#define IMGUI_DEFINE_MATH_OPERATORS #include +#include #include - +#include +#include +#include +#include #include -#include "soh/Enhancements/cosmetics/CosmeticsEditor.h" +#include namespace UIWidgets { - // MARK: - Layout Helper - - // Automatically adds newlines to break up text longer than a specified number of characters - // Manually included newlines will still be respected and reset the line length - // If line is midword when it hits the limit, text should break at the last encountered space - std::string WrappedText(const char* text, unsigned int charactersPerLine) { - std::string newText(text); - const size_t tipLength = newText.length(); - int lastSpace = -1; - int currentLineLength = 0; - for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { - if (newText[currentCharacter] == '\n') { - currentLineLength = 0; - lastSpace = -1; - continue; - } else if (newText[currentCharacter] == ' ') { - lastSpace = currentCharacter; - } - - if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { - newText[lastSpace] = '\n'; - currentLineLength = currentCharacter - lastSpace - 1; - lastSpace = -1; - } - currentLineLength++; +// Automatically adds newlines to break up text longer than a specified number of characters +// Manually included newlines will still be respected and reset the line length +// If line is midword when it hits the limit, text should break at the last encountered space +std::string WrappedText(const char* text, unsigned int charactersPerLine) { + std::string newText(text); + const size_t tipLength = newText.length(); + int lastSpace = -1; + int currentLineLength = 0; + for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { + if (newText[currentCharacter] == '\n') { + currentLineLength = 0; + lastSpace = -1; + continue; + } else if (newText[currentCharacter] == ' ') { + lastSpace = currentCharacter; } - return newText; + if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { + newText[lastSpace] = '\n'; + currentLineLength = currentCharacter - lastSpace - 1; + lastSpace = -1; + } + currentLineLength++; } - std::string WrappedText(const std::string& text, unsigned int charactersPerLine) { - return WrappedText(text.c_str(), charactersPerLine); + return newText; +} + +std::string WrappedText(const std::string& text, unsigned int charactersPerLine) { + return WrappedText(text.c_str(), charactersPerLine); +} + +void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { + if (padTop) { + Spacer(extraVerticalTopPadding); } - - void SetLastItemHoverText(const std::string& text) { - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - void SetLastItemHoverText(const char* text) { - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - // Adds a "?" next to the previous ImGui item with a custom tooltip - void InsertHelpHoverText(const std::string& text) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - void InsertHelpHoverText(const char* text) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - - // MARK: - UI Elements - - void Tooltip(const char* text) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", WrappedText(text).c_str()); - } - } - - void Spacer(float height) { - ImGui::Dummy(ImVec2(0.0f, height)); - } - - void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { - if (padTop) { - Spacer(extraVerticalTopPadding); - } - ImGui::Separator(); - if (padBottom) { - Spacer(extraVerticalBottomPadding); - } - } - - void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) { - float thickness = ImMax(sz / 5.0f, 1.0f); - sz -= thickness * 0.5f; - pos += ImVec2(thickness * 0.25f, thickness * 0.25f); - - draw_list->PathLineTo(ImVec2(pos.x, pos.y)); - draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y + sz)); - draw_list->PathStroke(col, 0, thickness); - - draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y)); - draw_list->PathLineTo(ImVec2(pos.x, pos.y + sz)); - draw_list->PathStroke(col, 0, thickness); - } - - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) { - return false; - } - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - const float square_sz = ImGui::GetFrameHeight(); - const ImVec2 pos = window->DC.CursorPos; - const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); - ImGui::ItemSize(total_bb, style.FramePadding.y); - if (!ImGui::ItemAdd(total_bb, id)) { - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); - return false; - } - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) { - *v = !(*v); - ImGui::MarkItemEdited(id); - } - - const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); - ImGui::RenderNavHighlight(total_bb, id); - ImGui::RenderFrame(check_bb.Min, check_bb.Max, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); - ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); - ImU32 cross_col = ImGui::GetColorU32(ImVec4(0.50f, 0.50f, 0.50f, 1.00f)); - bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; - if (mixed_value) { - // Undocumented tristate/mixed/indeterminate checkbox (#2644) - // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) - ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); - window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); - } else if ((!disabled && *v) || (disabled && disabledGraphic == CheckboxGraphics::Checkmark)) { - const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); - } else if ((!disabled && !*v && renderCrossWhenOff) || (disabled && disabledGraphic == CheckboxGraphics::Cross)) { - const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - //RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), disabled ? cross_col : check_col, square_sz - pad * 2.0f); // Caused confusion as to status - } - - ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); - if (g.LogEnabled) { - ImGui::LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); - } - if (label_size.x > 0.0f) { - ImGui::RenderText(label_pos, label); - } - - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); - return pressed; - } - - bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic) { - bool ret; - if (*v == 0) { - bool b = false; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 1; - } - } else if (*v == 1) { - ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); - bool b = true; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 2; - } - ImGui::PopItemFlag(); - } else if (*v == 2) { - bool b = true; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 0; - } - } else { - SPDLOG_INFO("Invalid CheckBoxTristate value: {}", *v); - *v = 0; - return false; - } - return ret; - } - - void ReEnableComponent(const char* disabledTooltipText) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(disabledTooltipText, "") != 0) { - ImGui::SetTooltip("%s", disabledTooltipText); - } - // End of disable region of previous component - ImGui::PopStyleVar(1); - ImGui::PopItemFlag(); - } - - void DisableComponent(const float alpha) { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - } - - bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - bool changed = false; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - bool val = (bool)CVarGetInteger(cvarName, defaultValue); - if (CustomCheckbox(text, &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - changed = true; - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - return changed; - } - - bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - bool changed = false; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - int val = CVarGetInteger(cvarName, defaultValue); - if (CustomCheckboxTristate(text, &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - return changed; - } - - bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - bool changed = EnhancementCheckbox(text, cvarName, disabled, disabledTooltipText, disabledGraphic, defaultValue); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { - bool changed = false; - if (defaultIndex <= 0) { - defaultIndex = 0; - } - - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - uint8_t selected = CVarGetInteger(cvarName, defaultIndex); - std::string comboName = std::string("##") + std::string(cvarName); - if (ImGui::BeginCombo(comboName.c_str(), comboArray[selected])) { - for (uint8_t i = 0; i < comboArray.size(); i++) { - if (strlen(comboArray[i]) > 0) { - if (ImGui::Selectable(comboArray[i], i == selected)) { - CVarSetInteger(cvarName, i); - selected = i; - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - } - } - } - ImGui::EndCombo(); - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - - if (disabledValue >= 0 && selected != disabledValue) { - CVarSetInteger(cvarName, disabledValue); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - - return changed; - } - - bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { - ImGui::Text("%s", label); - s32 currentValue = CVarGetInteger(cvarName, defaultIndex); - -#ifdef __WIIU__ - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 40.0f)); - ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 60.0f); -#else - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 20.0f)); - ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 30.0f); -#endif - - bool changed = EnhancementCombobox(cvarName, comboArray, defaultIndex, disabled, disabledTooltipText, disabledValue); - - ImGui::PopItemWidth(); - return changed; - } - - void PaddedText(const char* text, bool padTop, bool padBottom) { - if (padTop) Spacer(0); - - ImGui::Text("%s", text); - - if (padBottom) Spacer(0); - } - - bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { - bool changed = false; - int val = CVarGetInteger(cvarName, defaultValue); - const int oldVal = val; - - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - ImGui::Text(text, val); - Spacer(0); - - ImGui::BeginGroup(); - if (PlusMinusButton) { - std::string MinusBTNName = " - ##" + std::string(cvarName); - if (ImGui::Button(MinusBTNName.c_str())) { - val--; - changed = true; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - } - - ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth)); - if (ImGui::SliderInt(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) - { - changed = true; - } - ImGui::PopItemWidth(); - - if (PlusMinusButton) { - std::string PlusBTNName = " + ##" + std::string(cvarName); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val++; - changed = true; - } - } - ImGui::EndGroup(); - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - - if (val < min) { - val = min; - changed = true; - } - - if (val > max) { - val = max; - changed = true; - } - - if (changed && (oldVal != val)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - } else { - changed = false; - } - - return changed; - } - - bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { - bool changed = false; - float val = CVarGetFloat(cvarName, defaultValue); - const float oldVal = val; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - // Calculate how much precision to save based on the given range of the slider, limited to 6 decimal places - // Precision is also used when adding/subtracting using the +/- buttons - const float sliderWidth = std::min((ImGui::GetContentRegionAvail().x - 2.0f * (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth); - const float diff = (max - min) / sliderWidth; - int ticks = 0; - float increment = 1.0f; - if (diff < 1.0f) { - ticks++; - increment = 0.1f; - } - if (diff < 0.1f) { - ticks++; - increment = 0.01f; - } - if (diff < 0.01f) { - ticks++; - increment = 0.001f; - } - if (diff < 0.001f) { - ticks++; - increment = 0.0001f; - } - if (diff < 0.0001f) { - ticks++; - increment = 0.00001f; - } - if (diff < 0.00001f) { - ticks++; - increment = 0.000001f; - } - - if (!isPercentage) { - ImGui::Text(text, val); - } else { - ImGui::Text(text, val * 100.0f); - } - Spacer(0); - - ImGui::BeginGroup(); - if (PlusMinusButton) { - std::string MinusBTNName = " - ##" + std::string(cvarName); - if (ImGui::Button(MinusBTNName.c_str())) { - val -= increment; - changed = true; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - } - - ImGui::PushItemWidth(sliderWidth); - if (ImGui::SliderFloat(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) { - changed = true; - } - ImGui::PopItemWidth(); - - if (PlusMinusButton) { - std::string PlusBTNName = " + ##" + std::string(cvarName); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val += increment; - changed = true; - } - } - ImGui::EndGroup(); - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - - if (val < min) { - val = min; - changed = true; - } - - if (val > max) { - val = max; - changed = true; - } - - if (changed && !(abs(oldVal - val) < 0.000001f)) { - std::stringstream ss; - ss << std::setprecision(ticks + 1) << std::setiosflags(std::ios_base::fixed) << val; - val = std::stof(ss.str()); - CVarSetFloat(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } else { - changed = false; - } - - return changed; - } - - bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { - bool changed = false; - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - changed = EnhancementSliderInt(text, id, cvarName, min, max, format, defaultValue, PlusMinusButton, disabled, disabledTooltipText); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { - bool changed = false; - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - changed = EnhancementSliderFloat(text, id, cvarName, min, max, format, defaultValue, isPercentage, PlusMinusButton, disabled, disabledTooltipText); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool EnhancementRadioButton(const char* text, const char* cvarName, int id) { - /*Usage : - EnhancementRadioButton("My Visible Name",CVAR_GROUP("MyCVarName"), MyID); - First arg is the visible name of the Radio button - Second is the cvar name where MyID will be saved. - Note: the CVar name should be the same to each Buddies. - Example : - EnhancementRadioButton("English", CVAR_SETTING("Languages"), LANGUAGE_ENG); - EnhancementRadioButton("German", CVAR_SETTING("Languages"), LANGUAGE_GER); - EnhancementRadioButton("French", CVAR_SETTING("Languages"), LANGUAGE_FRA); - */ - std::string make_invisible = "##" + std::string(text) + std::string(cvarName); - - bool ret = false; - int val = CVarGetInteger(cvarName, 0); - if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { - CVarSetInteger(cvarName, id); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ret = true; - } - ImGui::SameLine(); - ImGui::Text("%s", text); - - return ret; - } - - bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) { - bool changed = false; - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string MakeInvisible = "Reset##" + std::string(cvarName) + "Reset"; - if (ImGui::Button(MakeInvisible.c_str())) { - colors->x = defaultcolors.x; - colors->y = defaultcolors.y; - colors->z = defaultcolors.z; - colors->w = has_alpha ? defaultcolors.w : 255.0f; - - Color_RGBA8 colorsRGBA; - colorsRGBA.r = defaultcolors.x; - colorsRGBA.g = defaultcolors.y; - colorsRGBA.b = defaultcolors.z; - colorsRGBA.a = has_alpha ? defaultcolors.w : 255.0f; - - CVarSetColor(cvarName, colorsRGBA); - CVarSetInteger(Cvar_RBM.c_str(), 0); //On click disable rainbow mode. - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color"); - return changed; - } - - bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors) { - bool changed = false; - Color_RGBA8 NewColors = {0,0,0,255}; - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string FullName = "Random##" + std::string(cvarName) + "Random"; - if (ImGui::Button(FullName.c_str())) { -#if defined(__SWITCH__) || defined(__WIIU__) - srand(time(NULL)); -#endif - ImVec4 color = GetRandomValue(); - colors->x = color.x; - colors->y = color.y; - colors->z = color.z; - NewColors.r = fmin(fmax(colors->x * 255, 0), 255); - NewColors.g = fmin(fmax(colors->y * 255, 0), 255); - NewColors.b = fmin(fmax(colors->z * 255, 0), 255); - CVarSetColor(cvarName, NewColors); - CVarSetInteger(Cvar_RBM.c_str(), 0); // On click disable rainbow mode. - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - Tooltip("Chooses a random color\nOverwrites previously chosen color"); - return changed; - } - - void DrawLockColorCheckbox(const char* cvarName) { - std::string Cvar_Lock = std::string(cvarName) + "Lock"; - s32 lock = CVarGetInteger(Cvar_Lock.c_str(), 0); - std::string FullName = "Lock##" + Cvar_Lock; - EnhancementCheckbox(FullName.c_str(), Cvar_Lock.c_str()); - Tooltip("Prevents this color from being changed upon selecting \"Randomize all\""); - } - - void RainbowColor(const char* cvarName, ImVec4* colors) { - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string MakeInvisible = "Rainbow##" + std::string(cvarName) + "Rainbow"; - - EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str()); - Tooltip("Cycles through colors on a timer\nOverwrites previously chosen color"); - } - - void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) { - Color_RGBA8 defaultColors; - defaultColors.r = default_colors.x; - defaultColors.g = default_colors.y; - defaultColors.b = default_colors.z; - defaultColors.a = default_colors.w; - - Color_RGBA8 cvarColor = CVarGetColor(cvarname, defaultColors); - - ColorArray.x = cvarColor.r / 255.0; - ColorArray.y = cvarColor.g / 255.0; - ColorArray.z = cvarColor.b / 255.0; - ColorArray.w = cvarColor.a / 255.0; - } - - bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow, bool has_alpha, bool TitleSameLine) { - bool changed = false; - LoadPickersColors(ColorRGBA, cvarName, default_colors, has_alpha); - - ImGuiColorEditFlags flags = ImGuiColorEditFlags_None; - - if (!TitleSameLine) { - ImGui::Text("%s", text); - flags = ImGuiColorEditFlags_NoLabel; - } - - ImGui::PushID(cvarName); - - if (!has_alpha) { - if (ImGui::ColorEdit3(text, (float*)&ColorRGBA, flags)) - { - Color_RGBA8 colors; - colors.r = ColorRGBA.x * 255.0; - colors.g = ColorRGBA.y * 255.0; - colors.b = ColorRGBA.z * 255.0; - colors.a = 255.0; - - CVarSetColor(cvarName, colors); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - } - else - { - flags |= ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview; - if (ImGui::ColorEdit4(text, (float*)&ColorRGBA, flags)) - { - Color_RGBA8 colors; - colors.r = ColorRGBA.x * 255.0; - colors.g = ColorRGBA.y * 255.0; - colors.b = ColorRGBA.z * 255.0; - colors.a = ColorRGBA.w * 255.0; - - CVarSetColor(cvarName, colors); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - } - - ImGui::PopID(); - - //ImGui::SameLine(); // Removing that one to gain some width spacing on the HUD editor - ImGui::PushItemWidth(-FLT_MIN); - if (DrawResetColorButton(cvarName, &ColorRGBA, default_colors, has_alpha)) { - changed = true; - } - ImGui::SameLine(); - if (DrawRandomizeColorButton(cvarName, &ColorRGBA)) { - changed = true; - } - if (allow_rainbow) { - if (ImGui::GetContentRegionAvail().x > 185) { - ImGui::SameLine(); - } - RainbowColor(cvarName, &ColorRGBA); - } - DrawLockColorCheckbox(cvarName); - ImGui::NewLine(); - ImGui::PopItemWidth(); - - return changed; - } - - void DrawFlagArray32(const std::string& name, uint32_t& flags) { - ImGui::PushID(name.c_str()); - for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint32_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - void DrawFlagArray16(const std::string& name, uint16_t& flags) { - ImGui::PushID(name.c_str()); - for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint16_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - void DrawFlagArray8(const std::string& name, uint8_t& flags) { - ImGui::PushID(name.c_str()); - for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint8_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - bool StateButtonEx(const char* str_id, const char* label, ImVec2 size, ImGuiButtonFlags flags) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImGuiStyle& style = g.Style; - const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - const ImGuiID id = window->GetID(str_id); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - const float default_size = ImGui::GetFrameHeight(); - ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); - if (!ImGui::ItemAdd(bb, id)) - return false; - - if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { - ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); - } - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); - - if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { - ImGui::PopItemFlag(); // ImGuiItemFlags_ButtonRepeat; - } - - // Render - const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive - : hovered ? ImGuiCol_ButtonHovered - : ImGuiCol_Button); - //const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); - ImGui::RenderNavHighlight(bb, id); - ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); - ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, {0.55f, 0.45f}, &bb); - /*ImGui::RenderArrow(window->DrawList, - bb.Min + - ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), - text_col, dir);*/ - - IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); - return pressed; - } - - bool StateButton(const char* str_id, const char* label) { - float sz = ImGui::GetFrameHeight(); - return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None); - } - - // Reference: imgui-src/misc/cpp/imgui_stdlib.cpp - int InputTextResizeCallback(ImGuiInputTextCallbackData* data) { - std::string* value = (std::string*)data->UserData; - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { - value->resize(data->BufTextLen); - data->Buf = (char*)value->c_str(); - } - return 0; - } - - bool InputString(const char* label, std::string* value) { - return ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value); + ImGui::Separator(); + if (padBottom) { + Spacer(extraVerticalBottomPadding); } } + +void Tooltip(const char* text) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", WrappedText(text).c_str()); + } +} + +void PushStyleMenu(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleColor(ImGuiCol_Border, ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f); +} + +void PushStyleMenu(Colors color) { + PushStyleMenu(ColorValues.at(color)); +} + +void PopStyleMenu() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); +} + +bool BeginMenu(const char* label, Colors color) { + bool dirty = false; + PushStyleMenu(color); + ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f, 0.0f), ImVec2(FLT_MAX, FLT_MAX)); + if (ImGui::BeginMenu(label)) { + dirty = true; + } + PopStyleMenu(); + return dirty; +} + +void PushStyleMenuItem(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, color); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f)); +} + +void PushStyleMenuItem(Colors color) { + PushStyleMenuItem(ColorValues.at(color)); +} + +void PopStyleMenuItem() { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); +} + +bool MenuItem(const char* label, const char* shortcut, Colors color) { + bool dirty = false; + PushStyleMenuItem(color); + if (ImGui::MenuItem(label, shortcut)) { + dirty = true; + } + PopStyleMenuItem(); + return dirty; +} + +void PushStyleButton(const ImVec4& color, const ImVec2 padding) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, padding); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleButton(Colors color, ImVec2 padding) { + PushStyleButton(ColorValues.at(color), padding); +} + +void PopStyleButton() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(4); +} + +void PushStyleInput(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleInput(Colors color) { + PushStyleInput(ColorValues.at(color)); +} + +void PopStyleInput() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(7); +} + +void PushStyleHeader(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); +} + +void PushStyleHeader(Colors color) { + PushStyleHeader(ColorValues.at(color)); +} + +void PopStyleHeader() { + ImGui::PopStyleColor(3); +} + +bool Button(const char* label, const ButtonOptions& options) { + ImGui::BeginDisabled(options.disabled); + PushStyleButton(options.color, options.padding); + bool dirty = ImGui::Button(label, options.size); + PopStyleButton(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return dirty; +} + +bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, + const WindowButtonOptions& options) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + std::string buttonText = label; + bool dirty = false; + if (CVarGetInteger(cvarName, 0)) { + buttonText = ICON_FA_WINDOW_CLOSE " " + buttonText; + } else { + buttonText = ICON_FA_EXTERNAL_LINK_SQUARE " " + buttonText; + } + if (Button(buttonText.c_str(), {{ options.tooltip, options.disabled, options.disabledTooltip, options.color }, + options.size, options.padding })) { + windowPtr->ToggleVisibility(); + dirty = true; + } + ImGui::PopStyleVar(); + return dirty; +} + +void PushStyleCheckbox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleCheckbox(Colors color) { + PushStyleCheckbox(ColorValues.at(color)); +} + +void PopStyleCheckbox() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(5); +} + +void Spacer(float height) { + ImGui::Dummy(ImVec2(0.0f, height)); +} + +void Separator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { + if (padTop) { + Spacer(extraVerticalTopPadding); + } + ImGui::Separator(); + if (padBottom) { + Spacer(extraVerticalBottomPadding); + } +} + +// Adds a "?" next to the previous ImGui item with a custom tooltip +void InsertHelpHoverText(const std::string& text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60).c_str()); + ImGui::EndTooltip(); + } +} + +void InsertHelpHoverText(const char* text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60).c_str()); + ImGui::EndTooltip(); + } +} + +void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) { + text_display_end = ImGui::FindRenderedTextEnd(text, text_end); + } else { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + if (text != text_display_end) { + window->DrawList->AddText(g.Font, g.FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + ImGui::LogRenderedText(&pos, text, text_display_end); + } +} + +bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGui::BeginDisabled(options.disabled); + + bool above = options.labelPosition == LabelPosition::Above; + bool lpFar = options.labelPosition == LabelPosition::Far; + bool right = options.alignment == ComponentAlignment::Right; + bool none = options.labelPosition == LabelPosition::None; + + std::string labelStr = (none ? "##" : ""); + labelStr.append(_label); + + const char* label = labelStr.c_str(); + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + const float square_sz = ImGui::GetFrameHeight(); + ImVec2 pos = window->DC.CursorPos; + + if (right) { + float labelOffsetX = (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + if (!lpFar) { + pos.x += ImGui::GetContentRegionAvail().x - (label_size.x + labelOffsetX); + } + } + float bbAboveX = lpFar ? ImGui::GetContentRegionAvail().x + : (label_size.x + (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz)); + float bbAboveY = label_size.y + (above ? square_sz : 0) + (style.FramePadding.y * 2.0f); + const ImRect total_bb(pos, pos + ImVec2(bbAboveX, bbAboveY)); + + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) { + ImGui::EndDisabled(); + return false; + } + bool hovered, held, pressed; + pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) { + *value = !(*value); + ImGui::MarkItemEdited(id); + } + PushStyleCheckbox(options.color); + ImVec2 checkPos = pos; + ImVec2 labelPos = pos; + if (options.labelPosition == LabelPosition::Above) { + checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f); + } else { + labelPos.y += (square_sz / 2) - (label_size.y / 2); + } + if (options.alignment == ComponentAlignment::Right) { + checkPos.x = total_bb.Max.x - square_sz; + } else { + float labelFarOffset = ImGui::GetContentRegionAvail().x - label_size.x; + float labelOffsetX = above ? 0 : (lpFar ? labelFarOffset : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + labelPos.x += labelOffsetX; + } + const ImRect check_bb(checkPos, checkPos + ImVec2(square_sz, square_sz)); + ImGui::RenderNavHighlight(total_bb, id); + ImGui::RenderFrame(check_bb.Min, check_bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive + : hovered ? ImGuiCol_FrameBgHovered + : ImGuiCol_FrameBg), + true, style.FrameRounding); + ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); + bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all + // widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } else if (*value) { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + RenderText(labelPos, label, ImGui::FindRenderedTextEnd(label), true); + PopStyleCheckbox(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return pressed; +} + +bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) { + bool dirty = false; + bool value = (bool)CVarGetInteger(cvarName, options.defaultValue); + if (Checkbox(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool StateButton(const char* str_id, const char* label, ImVec2 size, ButtonOptions options, ImGuiButtonFlags flags) { + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) { + return false; + } + + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = ImGui::GetFrameHeight(); + ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ImGui::ItemAdd(bb, id)) + return false; + + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { + ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); + } + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { + ImGui::PopItemFlag(); // ImGuiItemFlags_ButtonRepeat; + } + PushStyleButton(options.color); + // Render + const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button); + //const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + ImGui::RenderTextClipped(bb.Min + (style.FramePadding * 0.35f), bb.Max - (style.FramePadding / 4), label, NULL, &label_size, style.ButtonTextAlign, &bb); + PopStyleButton(); + /*ImGui::RenderArrow(window->DrawList, + bb.Min + + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), + text_col, dir);*/ + + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); + return pressed; +} + +float CalcComboWidth(const char* preview_value, ImGuiComboFlags flags) { + ImGuiContext& g = *GImGui; + + const ImGuiStyle& style = g.Style; + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together + if (flags & ImGuiComboFlags_WidthFitPreview) + IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : ImGui::GetFrameHeight(); + const float preview_width = ImGui::CalcTextSize(preview_value, NULL, true).x; + float w = arrow_size + preview_width + (style.FramePadding.x * 2.0f); + return w; +} + +void PushStyleCombobox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); +} + +void PushStyleCombobox(Colors color) { + PushStyleCombobox(ColorValues.at(color)); +} + +void PopStyleCombobox() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(9); +} + +void PushStyleTabs(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); +} + +void PushStyleTabs(Colors color) { + PushStyleTabs(ColorValues.at(color)); +} + +void PopStyleTabs() { + ImGui::PopStyleColor(6); + ImGui::PopStyleVar(4); +} + +void PushStyleSlider(Colors color_) { + const ImVec4& color = ColorValues.at(color_); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(1.0, 1.0, 1.0, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(1.0, 1.0, 1.0, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); +} + +void PopStyleSlider() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(6); +} + +bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + float width = (options.size == ImVec2(0,0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); + } + ImGui::AlignTextToFramePadding(); + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label, *value); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + if (options.clamp) { + if (*value < options.min) { + *value = options.min; + } + } + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(width - (ImGui::CalcTextSize("+").x + ImGui::GetStyle().FramePadding.x * 2 + 3) * 2); + } else { + ImGui::SetNextItemWidth(width); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &options.min, &options.max, options.format, + options.flags)) { + if (options.clamp) { + if (*value < options.min) { + *value = options.min; + } + if (*value > options.max) + *value = options.max; + } + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + if (options.clamp) { + if (*value > options.max) + *value = options.max; + } + dirty = true; + } + } + + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label, *value); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x); + ImGui::Text(label, *value); + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultValue); + if (SliderInt(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +void ClampFloat(float* value, float min, float max, float step) { + int ticks = 0; + float increment = 1.0f; + if (step < 1.0f) { + ticks++; + increment = 0.1f; + } + if (step < 0.1f) { + ticks++; + increment = 0.01f; + } + if (step < 0.01f) { + ticks++; + increment = 0.001f; + } + if (step < 0.001f) { + ticks++; + increment = 0.0001f; + } + if (step < 0.0001f) { + ticks++; + increment = 0.00001f; + } + if (step < 0.00001f) { + ticks++; + increment = 0.000001f; + } + int factor = 1 * std::pow(10, ticks); + if (*value < min) { + *value = min; + } else if (*value > max) { + *value = max; + } else { + int trunc = (int)std::round(*value * factor); + *value = (float)trunc / factor; + } +} + +bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + float valueToDisplay = options.isPercentage ? *value * 100.0f : *value; + float maxToDisplay = options.isPercentage ? options.max * 100.0f : options.max; + float minToDisplay = options.isPercentage ? options.min * 100.0f : options.min; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + float labelSpacing = ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x; + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); + } + ImGui::AlignTextToFramePadding(); + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label, *value); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } else if (options.labelPosition == LabelPosition::Near) { + width -= labelSpacing; + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + width -= labelSpacing; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(width - (ImGui::CalcTextSize("+").x + ImGui::GetStyle().FramePadding.x * 2 + 3) * 2); + } else { + ImGui::SetNextItemWidth(width); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_Float, &valueToDisplay, &minToDisplay, &maxToDisplay, + options.format, options.flags)) { + *value = options.isPercentage ? valueToDisplay / 100.0f : valueToDisplay; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + } + + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label, *value); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - labelSpacing); + ImGui::Text(label, *value); + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options) { + bool dirty = false; + float value = CVarGetFloat(cvarName, options.defaultValue); + if (SliderFloat(label, &value, options)) { + CVarSetFloat(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +int InputTextResizeCallback(ImGuiInputTextCallbackData* data) { + std::string* value = (std::string*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + value->resize(data->BufTextLen); + data->Buf = (char*)value->c_str(); + } + return 0; +} + +bool InputString(const char* label, std::string* value, const InputOptions& options) { + bool dirty = false; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleInput(options.color); + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value->c_str()); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(width - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value->c_str()); + } + } + ImGui::SetNextItemWidth(width); + if (ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value)) { + dirty = true; + } + if (value->empty() && !options.placeholder.empty()) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "%s", options.placeholder.c_str()); + } + PopStyleInput(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarInputString(const char* label, const char* cvarName, const InputOptions& options) { + bool dirty = false; + std::string value = CVarGetString(cvarName, options.defaultValue.c_str()); + if (InputString(label, &value, options)) { + CVarSetString(cvarName, value.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool InputInt(const char* label, int32_t* value, const InputOptions& options) { + bool dirty = false; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleInput(options.color); + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(width - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value); + } + } + ImGui::SetNextItemWidth(width); + if (ImGui::InputScalar(label, ImGuiDataType_S32, value)) { + dirty = true; + } + if ((ImGui::GetItemStatusFlags() & ImGuiItemStatusFlags_Edited) && !options.placeholder.empty()) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "%s", options.placeholder.c_str()); + } + PopStyleInput(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarInputInt(const char* label, const char* cvarName, const InputOptions& options) { + bool dirty = false; + int32_t defaultValue = std::stoi(options.defaultValue); + int32_t value = CVarGetInteger(cvarName, defaultValue); + if (InputInt(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor, bool hasAlpha, uint8_t modifiers, UIWidgets::Colors themeColor) { + std::string valueCVar = std::string(cvarName) + ".Value"; + std::string rainbowCVar = std::string(cvarName) + ".Rainbow"; + std::string lockedCVar = std::string(cvarName) + ".Locked"; + Color_RGBA8 color = CVarGetColor(valueCVar.c_str(), defaultColor); + ImVec4 colorVec = ImVec4(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f); + bool changed = false; + bool showReset = modifiers & ColorPickerResetButton; + bool showRandom = modifiers & ColorPickerRandomButton; + bool showRainbow = modifiers & ColorPickerRainbowCheck; + bool showLock = modifiers & ColorPickerLockCheck; + bool locked = CVarGetInteger(lockedCVar.c_str(), 0); + ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoInputs; + ImGui::BeginDisabled(locked); + PushStyleCombobox(UIWidgets::Colors::DarkGray); + if (hasAlpha) { + changed = ImGui::ColorEdit4(label, (float*)&colorVec, flags | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); + } else { + changed = ImGui::ColorEdit3(label, (float*)&colorVec, flags | ImGuiColorEditFlags_NoAlpha); + } + PopStyleCombobox(); + ImGui::AlignTextToFramePadding(); + if (showReset) { + ImGui::SameLine(); + std::string uniqueTag = "Reset##" + std::string(label); + if (UIWidgets::Button(uniqueTag.c_str(), UIWidgets::ButtonOptions({{ .tooltip = "Resets this color to its default value" }} ).Color(themeColor).Size(UIWidgets::Sizes::Inline))) { + CVarClearBlock(valueCVar.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + if (showRandom) { + ImGui::SameLine(); + std::string uniqueTag = "Random##" + std::string(label); + if (UIWidgets::Button(uniqueTag.c_str(), UIWidgets::ButtonOptions({{ .tooltip = "Generates a random color value to use" }}).Color(themeColor).Size(UIWidgets::Sizes::Inline))) { + colorVec = GetRandomValue(); + color.r = fmin(fmax(colorVec.x * 255, 0), 255); + color.g = fmin(fmax(colorVec.y * 255, 0), 255); + color.b = fmin(fmax(colorVec.z * 255, 0), 255); + CVarSetColor(valueCVar.c_str(), color); + CVarSetInteger(rainbowCVar.c_str(), 0); // On click disable rainbow mode. + ShipInit::Init(rainbowCVar.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + if (showRainbow) { + ImGui::SameLine(); + std::string uniqueTag = "Rainbow##" + std::string(cvarName) + "Rainbow"; + + UIWidgets::CVarCheckbox(uniqueTag.c_str(), rainbowCVar.c_str(), UIWidgets::CheckboxOptions({{ .tooltip = "Cycles through colors on a timer\nOverwrites previously chosen color" }}).Color(themeColor)); + } + ImGui::EndDisabled(); + if (showLock) { + ImGui::SameLine(); + std::string uniqueTag = "Lock##" + std::string(cvarName) + "Locked"; + + UIWidgets::CVarCheckbox(uniqueTag.c_str(), lockedCVar.c_str(), UIWidgets::CheckboxOptions({{ .tooltip = "Prevents this color from being changed" }}).Color(themeColor)); + } + if (changed) { + color.r = (uint8_t)(colorVec.x * 255.0f); + color.g = (uint8_t)(colorVec.y * 255.0f); + color.b = (uint8_t)(colorVec.z * 255.0f); + color.a = (uint8_t)(colorVec.w * 255.0f); + CVarSetColor(valueCVar.c_str(), color); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(valueCVar.c_str()); + changed = true; + } + + return changed; +} + +bool RadioButton(const char* label, bool active, const RadioButtonsOptions& options) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = IM_ROUND(center.x); + center.y = IM_ROUND(center.y); + const float radius = (square_sz - 1.0f) * 0.5f; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + ImGui::MarkItemEdited(id); + + ImGui::RenderNavCursor(total_bb, id); + const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); + window->DrawList->AddCircleFilled(center, radius, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); + if (active) + { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius - pad, ImGui::GetColorU32(ImGuiCol_CheckMark)); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center + ImVec2(1, 1), radius, ImGui::GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, ImGui::GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + ImGui::LogRenderedText(&label_pos, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(label_pos, label, ImGui::FindRenderedTextEnd(label), true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +bool CVarRadioButton(const char* text, const char* cvarName, int32_t id, const RadioButtonsOptions& options) { + std::string make_invisible = "##" + std::string(text) + std::string(cvarName); + + bool ret = false; + int val = CVarGetInteger(cvarName, 0); + PushStyleCheckbox(options.color); + if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { + CVarSetInteger(cvarName, id); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ret = true; + } + ImGui::SameLine(); + ImGui::Text("%s", text); + PopStyleCheckbox(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + + return ret; +} + +void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint32_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint16_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} +} // namespace UIWidgets + +ImVec4 GetRandomValue() { +#if !defined(__SWITCH__) && !defined(__WIIU__) + std::random_device rd; + std::mt19937 rng(rd()); +#else + size_t seed = std::hash{}(std::to_string(rand())); + std::mt19937_64 rng(seed); +#endif + std::uniform_int_distribution dist(0, 255 - 1); + + ImVec4 NewColor; + NewColor.x = (float)(dist(rng)) / 255.0f; + NewColor.y = (float)(dist(rng)) / 255.0f; + NewColor.z = (float)(dist(rng)) / 255.0f; + return NewColor; +} + +Color_RGBA8 RGBA8FromVec(ImVec4 vec) { + Color_RGBA8 color = { vec.x * 255, vec.y * 255, vec.z * 255, vec.w * 255 }; + return color; +} + +ImVec4 VecFromRGBA8(Color_RGBA8 color) { + ImVec4 vec = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f }; + return vec; +} diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 97a367270..ebf483194 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -1,16 +1,21 @@ -#ifndef UIWidgets_hpp -#define UIWidgets_hpp +#ifndef UIWidgets2_hpp +#define UIWidgets2_hpp #include -#include #include #include #include +#define IMGUI_DEFINE_MATH_OPERATORS #include +#include +#include +#include "soh/ShipUtils.h" #include "soh/ShipInit.hpp" namespace UIWidgets { + using SectionFunc = void(*)(); + struct TextFilters { static int FilterNumbers(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { @@ -26,74 +31,963 @@ namespace UIWidgets { } return 1; } + }; + + std::string WrappedText(const char* text, unsigned int charactersPerLine = 80); + std::string WrappedText(const std::string& text, unsigned int charactersPerLine = 80); + void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); + void Tooltip(const char* text); + + typedef enum ColorPickerModifiers { + ColorPickerResetButton = 1, + ColorPickerRandomButton = 2, + ColorPickerRainbowCheck = 4, + ColorPickerLockCheck = 8, + } ColorPickerModifiers; + + // mostly in order for colors usable by the menu without custom text color + enum Colors { + Red, + DarkRed, + Orange, + Green, + DarkGreen, + LightBlue, + Blue, + DarkBlue, + Indigo, + Violet, + Purple, + Brown, + Gray, + DarkGray, + // not suitable for menu theme use + Pink, + Yellow, + Cyan, + Black, + LightGray, + White, + NoColor + }; + + enum InputTypes { + String, + Scalar + }; + + const std::unordered_map ColorValues = { + { Colors::Pink, ImVec4(0.87f, 0.3f, 0.87f, 1.0f) }, + { Colors::Red, ImVec4(0.55f, 0.0f, 0.0f, 1.0f) }, + { Colors::DarkRed, ImVec4(0.3f, 0.0f, 0.0f, 1.0f) }, + { Colors::Orange, ImVec4(0.85f, 0.55f, 0.0f, 1.0f) }, + { Colors::Yellow, ImVec4(0.95f, 0.95f, 0.0f, 1.0f) }, + { Colors::Green, ImVec4(0.0f, 0.55f, 0.0f, 1.0f) }, + { Colors::DarkGreen, ImVec4(0.0f, 0.3f, 0.0f, 1.0f) }, + { Colors::Cyan, ImVec4(0.0f, 0.9f, 0.9f, 1.0f) }, + { Colors::LightBlue, ImVec4(0.0f, 0.24f, 0.8f, 1.0f) }, + { Colors::Blue, ImVec4(0.08f, 0.03f, 0.65f, 1.0f) }, + { Colors::DarkBlue, ImVec4(0.03f, 0.0f, 0.5f, 1.0f) }, + { Colors::Indigo, ImVec4(0.35f, 0.0f, 0.87f, 1.0f) }, + { Colors::Violet, ImVec4(0.5f, 0.0f, 0.9f, 1.0f) }, + { Colors::Purple, ImVec4(0.31f, 0.0f, 0.67f, 1.0f) }, + { Colors::Brown, ImVec4(0.37f, 0.18f, 0.0f, 1.0f) }, + { Colors::LightGray, ImVec4(0.75f, 0.75f, 0.75f, 1.0f) }, + { Colors::Gray, ImVec4(0.45f, 0.45f, 0.45f, 1.0f) }, + { Colors::DarkGray, ImVec4(0.15f, 0.15f, 0.15f, 1.0f) }, + { Colors::Black, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)}, + { Colors::White, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { Colors::NoColor, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)}, + }; + + namespace Sizes { + const ImVec2 Inline = ImVec2(0.0f, 0.0f); + const ImVec2 Fill = ImVec2(-1.0f, 0.0f); + } + + enum LabelPosition { + Near, + Far, + Above, + None, + Within, + }; + + enum ComponentAlignment { + Left, + Right, + }; + + struct WidgetOptions{ + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + Colors color = Colors::NoColor; + + WidgetOptions& Color(Colors color_) { + color = color = color_; + return *this; + } + WidgetOptions& Tooltip(const char* tooltip_) { + tooltip = tooltip_; + return *this; + } + WidgetOptions& Disabled(bool disabled_) { + disabled = disabled_; + return *this; + } + WidgetOptions& DisabledTooltip(const char* disabledTooltip_) { + disabledTooltip = disabledTooltip_; + return *this; + } + }; + + struct ButtonOptions : WidgetOptions { + ImVec2 size = Sizes::Fill; + ImVec2 padding = ImVec2(10.0f, 8.0f); + Colors color = Colors::Gray; + + ButtonOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + ButtonOptions& Padding(ImVec2 padding_) { + padding = padding_; + return *this; + } + ButtonOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ButtonOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct WindowButtonOptions : WidgetOptions { + ImVec2 size = Sizes::Inline; + ImVec2 padding = ImVec2(10.0f, 8.0f); + Colors color = Colors::Gray; + bool showButton = true; + bool embedWindow = true; + + WindowButtonOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + WindowButtonOptions& Padding(ImVec2 padding_) { + padding = padding_; + return *this; + } + WindowButtonOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + WindowButtonOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + WindowButtonOptions& ShowButton(bool showButton_) { + showButton = showButton_; + return *this; + } + WindowButtonOptions& EmbedWindow(bool embedWindow_) { + embedWindow = embedWindow_; + return *this; + } + }; + + struct CheckboxOptions : WidgetOptions { + bool defaultValue = false; // Only applicable to CVarCheckbox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Near; + Colors color = WidgetOptions::color = Colors::LightBlue; + + CheckboxOptions& DefaultValue(bool defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + CheckboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + CheckboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + CheckboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + CheckboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + CheckboxOptions& DisabledTooltip(const char* disabledTooltip_) { + WidgetOptions::disabledTooltip = disabledTooltip_; + return *this; + } + }; + + struct ComboboxOptions : WidgetOptions { + std::unordered_map comboMap = {}; + uint32_t defaultIndex = 0; // Only applicable to CVarCombobox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + ImGuiComboFlags flags = 0; + Colors color = Colors::LightBlue; + + ComboboxOptions& ComboMap(std::unordered_map comboMap_) { + comboMap = comboMap_; + return *this; + } + ComboboxOptions& DefaultIndex(uint32_t defaultIndex_) { + defaultIndex = defaultIndex_; + return *this; + } + ComboboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + ComboboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + ComboboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ComboboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct IntSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%d"; + int32_t step = 1; + int32_t min = 1; + int32_t max = 10; + int32_t defaultValue = 1; + bool clamp = true; + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + ImVec2 size = {0,0}; + + IntSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + IntSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + IntSliderOptions& Step(int32_t step_) { + step = step_; + return *this; + } + IntSliderOptions& Min(int32_t min_) { + min = min_; + return *this; + } + IntSliderOptions& Max(int32_t max_) { + max = max_; + return *this; + } + IntSliderOptions& DefaultValue(int32_t defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + IntSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + IntSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + IntSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + IntSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + IntSliderOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + IntSliderOptions& Clamp(bool clamp_) { + clamp = clamp_; + return *this; + } + }; + + struct FloatSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%f"; + float step = 0.01f; + float min = 0.01f; + float max = 10.0f; + float defaultValue = 1.0f; + bool clamp = true; + bool isPercentage = false; // Multiplies visual value by 100 + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + ImVec2 size = {0,0}; + + FloatSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + FloatSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + FloatSliderOptions& Step(float step_) { + step = step_; + return *this; + } + FloatSliderOptions& Min(float min_) { + min = min_; + return *this; + } + FloatSliderOptions& Max(float max_) { + max = max_; + return *this; + } + FloatSliderOptions& DefaultValue(float defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + FloatSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + FloatSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + FloatSliderOptions& IsPercentage(bool isPercentage_ = true) { + isPercentage = isPercentage_; + format = "%.0f%%"; + min = 0.0f; + max = 1.0f; + return *this; + } + FloatSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + FloatSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + FloatSliderOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + FloatSliderOptions& Clamp(bool clamp_) { + clamp = clamp_; + return *this; + } + }; + + struct RadioButtonsOptions : WidgetOptions { + std::unordered_map buttonMap; + RadioButtonsOptions& ButtonMap(std::unordered_map buttonMap_) { + buttonMap = buttonMap_; + return *this; + } + RadioButtonsOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + RadioButtonsOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } }; - // MARK: - Enums + struct InputOptions : WidgetOptions { + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImVec2 size = {0,0}; + std::string placeholder = ""; + InputTypes type = InputTypes::String; + std::string defaultValue = ""; - enum class CheckboxGraphics { - Cross, - Checkmark, - None + InputOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + InputOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + InputOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + + InputOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + + InputOptions& PlaceholderText(std::string&& placeholder_) { + placeholder = std::move(placeholder_); + return *this; + } + + InputOptions& PlaceholderText(std::string& placeholder_) { + placeholder = placeholder_; + return *this; + } + + InputOptions& InputType(InputTypes type_) { + type = type_; + return *this; + } + + InputOptions& DefaultValue(std::string defaultValue_) { + defaultValue = defaultValue_; + return *this; + } }; - constexpr float maxSliderWidth = 260.0f; -#ifdef __SWITCH__ - constexpr float sliderButtonWidth = 42.0f; -#elif defined(__WIIU__) - constexpr float sliderButtonWidth = 60.0f; -#else - constexpr float sliderButtonWidth = 30.0f; -#endif - std::string WrappedText(const char* text, unsigned int charactersPerLine = 60); - std::string WrappedText(const std::string& text, unsigned int charactersPerLine); + void PushStyleMenu(const ImVec4& color); + void PushStyleMenu(Colors color = Colors::LightBlue); + void PopStyleMenu(); + bool BeginMenu(const char* label, Colors color = Colors::LightBlue); - void SetLastItemHoverText(const std::string& text); - void SetLastItemHoverText(const char* text); + void PushStyleMenuItem(const ImVec4& color); + void PushStyleMenuItem(Colors color = Colors::LightBlue); + void PopStyleMenuItem(); + bool MenuItem(const char* label, const char* shortcut = NULL, Colors color = Colors::LightBlue); + + void PushStyleButton(const ImVec4& color, ImVec2 padding = ImVec2(10.0f, 8.0f)); + void PushStyleButton(Colors color = Colors::Gray, ImVec2 padding = ImVec2(10.0f, 8.0f)); + void PopStyleButton(); + bool Button(const char* label, const ButtonOptions& options = {}); + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const WindowButtonOptions& options = {}); + + void PushStyleCheckbox(const ImVec4& color); + void PushStyleCheckbox(Colors color = Colors::LightBlue); + void PopStyleCheckbox(); + void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash); + bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {}); + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {}); + + void PushStyleCombobox(const ImVec4& color); + void PushStyleCombobox(Colors color = Colors::LightBlue); + void PopStyleCombobox(); + + void PushStyleTabs(const ImVec4& color); + void PushStyleTabs(Colors color = Colors::LightBlue); + void PopStyleTabs(); + + void PushStyleInput(const ImVec4& color); + void PushStyleInput(Colors color = Colors::LightBlue); + void PopStyleInput(); + + void PushStyleHeader(const ImVec4& color); + void PushStyleHeader(Colors color = Colors::LightBlue); + void PopStyleHeader(); + + void Spacer(float height = 0.0f); + void Separator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, + float extraVerticalBottomPadding = 0.0f); + + /*using ComboVariant = std::variant&, const std::vector&>; + + bool Combobox(const char* label, int32_t* value, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text("%s", label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboSource, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + }*/ + + float CalcComboWidth(const char* preview_value, ImGuiComboFlags flags); + + template + bool Combobox(const char* label, T* value, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& [index, string] : comboMap) { + int len = strlen(string); + if (len > length) { + longest = string; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& string : comboVector) { + int len = strlen(string); + if (len > length) { + longest = string; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < comboVector.size(); ++i) { + auto newValue = static_cast(i); + if (strlen(comboVector.at(i)) > 1) { + if (ImGui::Selectable(comboVector.at(i), newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboVector.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& string : comboVector) { + int len = string.length(); + if (len > length) { + longest = string.c_str(); + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex).c_str(), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < comboVector.size(); ++i) { + auto newValue = static_cast(i); + if (comboVector.at(i).length() > 1) { + if (ImGui::Selectable(comboVector.at(i).c_str(), newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboVector.at(*value).c_str()).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + if (currentValueIndex >= N) { + currentValueIndex = 0; + } + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (size_t i = 0; i < N; i++) { + int len = strlen(comboArray[i]); + if (len > length) { + longest = comboArray[i]; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboArray[currentValueIndex], options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < N; ++i) { + auto newValue = static_cast(i); + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboMap, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboVector, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboArray, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + void PushStyleSlider(Colors color = Colors::LightBlue); + void PopStyleSlider(); + bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options = {}); + bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options = {}); + bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options = {}); + bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options = {}); + bool InputString(const char* label, std::string* value, const InputOptions& options = {}); + bool CVarInputString(const char* label, const char* cvarName, const InputOptions& options = {}); + bool InputInt(const char* label, int32_t* value, const InputOptions& options = {}); + bool CVarInputInt(const char* label, const char* cvarName, const InputOptions& options = {}); + bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor, bool hasAlpha = false, uint8_t modifiers = 0, UIWidgets::Colors themeColor = UIWidgets::Colors::LightBlue); + bool RadioButton(const char* label, bool active); + bool CVarRadioButton(const char* text, const char* cvarName, int32_t id, const RadioButtonsOptions& options); + bool StateButton(const char* str_id, const char* label, ImVec2 size, UIWidgets::ButtonOptions options, ImGuiButtonFlags flags = ImGuiButtonFlags_None); + void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); void InsertHelpHoverText(const std::string& text); void InsertHelpHoverText(const char* text); - - void Tooltip(const char* text); - void Spacer(float height); - void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); - - void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff = false); - bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic); - - void ReEnableComponent(const char* disabledTooltipText); - void DisableComponent(const float alpha); - - bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - - bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); - bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); - - void PaddedText(const char* text, bool padTop = true, bool padBottom = true); - - bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); - bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); - bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); - bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); - - bool EnhancementRadioButton(const char* text, const char* cvarName, int id); - - bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha); - bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors); - void DrawLockColorCheckbox(const char* cvarName); - void RainbowColor(const char* cvarName, ImVec4* colors); - - void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha); - bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha = false, bool TitleSameLine = false); - - void DrawFlagArray32(const std::string& name, uint32_t& flags); - void DrawFlagArray16(const std::string& name, uint16_t& flags); - void DrawFlagArray8(const std::string& name, uint8_t& flags); - bool StateButton(const char* str_id, const char* label); - bool InputString(const char* label, std::string* value); } +ImVec4 GetRandomValue(); + +Color_RGBA8 RGBA8FromVec(ImVec4 vec); +ImVec4 VecFromRGBA8(Color_RGBA8 color); #endif /* UIWidgets_hpp */ diff --git a/soh/soh/config/ConfigMigrators.h b/soh/soh/config/ConfigMigrators.h index 29cbb0774..008d5692a 100644 --- a/soh/soh/config/ConfigMigrators.h +++ b/soh/soh/config/ConfigMigrators.h @@ -16,7 +16,7 @@ namespace SOH { std::vector version3Migrations = { { MigrationAction::Rename, "gSwitchAge", "gGeneral.SwitchAge" }, - { MigrationAction::Rename, "gFrameAdvance", "gGeneral.FrameAdvance" }, + { MigrationAction::Rename, "gFrameAdvance", "gDeveloperTools.FrameAdvanceTick" }, { MigrationAction::Rename, "gRandoGenerating", "gGeneral.RandoGenerating" }, { MigrationAction::Rename, "gNewSeedGenerated", "gGeneral.NewSeedGenerated" }, { MigrationAction::Rename, "gOnFileSelectNameEntry", "gGeneral.OnFileSelectNameEntry" }, @@ -42,14 +42,12 @@ namespace SOH { { MigrationAction::Rename, "gCheckTrackerSettingsEnabled", "gOpenWindows.CheckTrackerSettings" }, { MigrationAction::Rename, "gCollisionViewerEnabled", "gOpenWindows.CollisionViewer" }, { MigrationAction::Rename, "gCosmeticsEditorEnabled", "gOpenWindows.CosmeticsEditor" }, - { MigrationAction::Rename, "gDLViewerEnabled", "gOpenWindows.DLViewer" }, + { MigrationAction::Rename, "gDLViewerEnabled", "gOpenWindows.DisplayListViewer" }, { MigrationAction::Rename, "gEntranceTrackerEnabled", "gOpenWindows.EntranceTracker" }, { MigrationAction::Rename, "gGameplayStatsEnabled", "gOpenWindows.GameplayStats" }, { MigrationAction::Rename, "gItemTrackerEnabled", "gOpenWindows.ItemTracker" }, { MigrationAction::Rename, "gItemTrackerSettingsEnabled", "gOpenWindows.ItemTrackerSettings" }, { MigrationAction::Rename, "gMessageViewerEnabled", "gOpenWindows.MessageViewer" }, - { MigrationAction::Rename, "gOpenWindows.InputViewer", "gOpenWindows.InputViewer" }, - { MigrationAction::Rename, "gOpenWindows.InputViewerSettings", "gOpenWindows.InputViewerSettings" }, { MigrationAction::Rename, "gRandomizerSettingsEnabled", "gOpenWindows.RandomizerSettings" }, { MigrationAction::Rename, "gSaveEditorEnabled", "gOpenWindows.SaveEditor" }, { MigrationAction::Rename, "gValueViewer.WindowOpen", "gOpenWindows.ValueViewer" }, diff --git a/soh/soh/cvar_prefixes.h b/soh/soh/cvar_prefixes.h index 6ddde97bd..4b8d9f93a 100644 --- a/soh/soh/cvar_prefixes.h +++ b/soh/soh/cvar_prefixes.h @@ -7,11 +7,11 @@ #define CVAR_SETTING(var) CVAR_PREFIX_SETTING "." var #define CVAR_WINDOW(var) CVAR_PREFIX_WINDOW "." var #define CVAR_TRACKER(var) CVAR_PREFIX_TRACKER "." var -#define CVAR_TRACKER_ITEM(var) CVAR_TRACKER(".ItemTracker." var) -#define CVAR_TRACKER_CHECK(var) CVAR_TRACKER(".CheckTracker." var) -#define CVAR_TRACKER_ENTRANCE(var) CVAR_TRACKER(".EntranceTracker." var) +#define CVAR_TRACKER_ITEM(var) CVAR_TRACKER("ItemTracker." var) +#define CVAR_TRACKER_CHECK(var) CVAR_TRACKER("CheckTracker." var) +#define CVAR_TRACKER_ENTRANCE(var) CVAR_TRACKER("EntranceTracker." var) #define CVAR_DEVELOPER_TOOLS(var) CVAR_PREFIX_DEVELOPER_TOOLS "." var #define CVAR_GENERAL(var) CVAR_PREFIX_GENERAL "." var #define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var -#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE(".CrowdControl." var) -#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE(".Sail." var) \ No newline at end of file +#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE("CrowdControl." var) +#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE("Sail." var) \ No newline at end of file diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c index 430c463f8..7e334a919 100644 --- a/soh/src/code/audio_playback.c +++ b/soh/src/code/audio_playback.c @@ -95,7 +95,7 @@ void Audio_InitNoteSub(Note* note, NoteSubEu* sub, NoteSubAttributes* attrs) { vel = 0.0f > vel ? 0.0f : vel; vel = 1.0f < vel ? 1.0f : vel; - float master_vol = (float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 100) / 100.0f; + float master_vol = (float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 40) / 100.0f; sub->targetVolLeft = (s32)((vel * volLeft) * (0x1000 - 0.001f)) * master_vol; sub->targetVolRight = (s32)((vel * volRight) * (0x1000 - 0.001f)) * master_vol; diff --git a/soh/src/code/z_frame_advance.c b/soh/src/code/z_frame_advance.c index 6fcd76ae7..7f9f26fda 100644 --- a/soh/src/code/z_frame_advance.c +++ b/soh/src/code/z_frame_advance.c @@ -18,10 +18,11 @@ s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input) { frameAdvCtx->enabled = !frameAdvCtx->enabled; } - if (!frameAdvCtx->enabled || CVarGetInteger(CVAR_GENERAL("FrameAdvance"), 0) || (CHECK_BTN_ALL(input->cur.button, BTN_Z) && + if (!frameAdvCtx->enabled || CVarGetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 0) || + (CHECK_BTN_ALL(input->cur.button, BTN_Z) && (CHECK_BTN_ALL(input->press.button, BTN_R) || (CHECK_BTN_ALL(input->cur.button, BTN_R) && (++frameAdvCtx->timer >= 9))))) { - CVarClear(CVAR_GENERAL("FrameAdvance")); + CVarClear(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick")); frameAdvCtx->timer = 0; return true; }