From 71c2c94ece12c664fe54f9959cce40776ba36dcd Mon Sep 17 00:00:00 2001 From: Malkierian Date: Wed, 2 Oct 2024 17:25:26 -0700 Subject: [PATCH] Modern menu (#701) * Start the framework. * Start header bar. * Header buttons now toggle. Sections show accordingly. Basic audio section done. Needs subdividing for sane widget widths. * Add fonts and multiple sizes. Add graphics settings to menu. * Finalized fonts, more sizes. * Modularized the menu structure. Has an errant EndChild somewhere. * Finished first step modularization. Parameterized menu height. * Some adaptive menu size. * Moved all enhancements over and organized. Tried to add button option for sidebar entries. * Finished Enhancements Menu. Sidebar window button still broken (can't reference window variables properly, not drawing in the right place). * Developer Tools header started. Menu now pops out with a checkbox in Developer Tools. Menu sizing works for smaller game sizes. Popout docking disabled. Port bool added for disallowing popout regardless of cvar. * Better auto-sizing. Columns collapse to 1 below 800 window width, and scroll. * Move inside of HudEditorWindow::DrawElement to DrawContents, and call DrawContents from DrawElement. * Menu resizability complete. Menu popout complete. Menu popout position/size is retained when popped in. Apply HUD editor changes, draw contents in menu if window is closed. All child sizes properly calculated. SidebarEntry drawing separated into its own custom element. * Split header entries into separate widget. Header now dynamically sized and shows scrollbar when necessary. * Modify EventLog to be drawable with the DrawContents setup. Prevent menu from covering other windows (always in back). * Add reset, close menu, and quit buttons to the header. * Fix reset and quit button placement. * Swap Actor Viewer, Collision Viewer, Save Editor to DrawContents concept. Change reset icon to FA_ICON_UNDO (single arrow) and quit icon to FA_ICON_POWER_OFF. Add button to close menu button tooltip, add parentheses to button for reset tooltip. * Custom InputEditorWindow to work with menu. Fix LUS references in Menu.cpp for Window backend changes. Implement gamepad navigation and forced cursor visibility checkboxes. Toggle cursor visibility according to menu status and forced visibility CVar. * clang-format * Swap std for fmt (runner build error). Ensure cursor visibility is restored if started in full screen with the menu open. * Oops, still had #include * Swap to LUS GetMenu() functionality. Add option to hide "F1 to open enhancements menu" text at startup if menus aren't open. * Remove last vestiges of pre-LUS-menu functionality. * cleanup * Adapt to GuiElement, GuiWindow changes on the menu support PR. * LUS PR bump. Adapt to cursor visibility changes in LUS. Adapt to menu toggle changes in LUS. Fix input editor not hiding in menu when popped out. * Add new enhancements and cheats to Modern Menu from rebase. * clang * Apply Graphics Menu Lag Fix to Menu structure. * Remove unnecessary window children to simplify controller navigation. * clang * Add `UpdateWindowBackendObjects()` call to backend combobox. * Rebase to latest develop. Remove redundant window backend stuff that exists now in BenMenuBar. * Search Enhancement (#2) * 90% complete, Commit for review. * Updated remaining Menu Entries, Reverted Backends and other Window Buttons for now. Motion Blur copied to Search Menu. * Did I mention I dislike this clang thing? lol * Updating MotionBlur options and reverting soem things. * Integrates .disabled and .disabledTooltip to the constructors. Allows for multiple qualifications to be checked against for disabling widgets and for setting the tooltip accordingly. * Updated Disabled Reasoning * Better Disabled State registration system * Clean up from testing * Expand WidgetOptions to be able to handle string and float values. bool and uint32_t are handled through int32_t. Numerous renames. Added several data structures to allow for dynamic updating of CVars for DisableOptions, as well as dynamic evaluation of status with enumerated conditions (less than, greater than, equal to). Added menuThemeIndex variable to store state of menu theme selection instead of having all the individual calls to CVarGetInteger for it. Updated once per frame. * Changed color vector to map with enum as key and UIWidget::Colors as the value. More renames. Moved float slider values into case block. * Add Color_RGB8 and Color_RGBA8 to CVarVariant in preparation for adding color widgets to menu item system. Not happy it required comparison operator overrides, but it is what it is. * Changed disable conditions to lambdas. * Convert Stats and Collision Viewer to menu item system. Swap out DisableOption vector for activeDisables and a lambda function for evaluating disabled and hidden. Set the precedent to still use the DisableInfo system. * Add DisableOption for both camera settings being off. Put Debug Camera in Free Look column, disable common widgets for both cameras, hide widgets specific to either if off. * Moved more widgets to MenuItem system. Some need proper initialization routines because of Ship::Context references. * Convert menu items to nested vector setup. Remove MenuItem enum. Add menu item location display to search results. * Partial removal of SidebarEntryIndex enum. * Finish removing SidebarEntryIndex. Rework drawing code to handle the change back to direct vector access. Add tooltips to search. * Fix Graphics enhancements section title. * Copy added menu items to modern menu system. * clang * Add some documentation for the menu system components. * Revert change to std::map * `size_t` because GCC has to be happy * Fixed HUD Editor Widescreen mode not displaying the sliders properly. Changed all windows to full-section-width display. * Revert changes to MotionBlur.cpp * Add non-CVar variants of checkbox, combobox, and the sliders. Fix motion blur functionality with new non-CVar widgets. * Add non-CVar variants of checkbox, combobox, and the sliders. Fix motion blur functionality with those non-CVar widgets. Add size and labelPosition to widgetOptions, implemented on button and checkbox/combobox/sliders. Change window popout buttons to inline size type. * Rename SearchMenu.h to SearchableMenuItems.h and move to BenGui folder. * Forgot to change the include XD * Change `modifierFunc` to `preFunc`, updated documentation for that and including `postFunc`. * Modify code to require variable for non-CVar widget initialization when necessary. Check for `valuePointer` being nullptr, and preventing widget drawing with a debug assert and error log entry. Remove all `has_variant` calls and wrap entire widget type switch in `SearchMenuGetItem` with a try/catch on `std::bad_variant_access` with another error log and debug assert. * More review changes. * Implement `isPercentage`, `format`, and `showButtons` for sliders. Pull values of those over from menubar. Add documentation on `widgetOptions` for those and others added for previous review changes. Move Fast Magic Arrow and Instant Fin Recall to Gameplay section. * Modify header and sidebar "buttons" to match theming of regular buttons. * Add precision clamping to SliderFloat value. At least 1.0 seems to be consistent at "100%". Change IR slider to be percentage. * Mention weirdness with SDL and pad menu button in controller nav tooltip. * Move Menu files to 2s2h/BenGui. * Add Warp Points to Developer Tools. Make "Press F1" prompt show only when a menu isn't registered. * clang * Make search input take focus when search menu is open. * Forgot include. * Global search (#3) * Restore Sidebar search entry, but lock it behind a checkbox in Settings. Dynamically adds or removes the sidebar entry, and header search is toggled inversely. * Make menu toggle its visibility when using the button in the menu to close. Couldn't handle Alt+F4 without a hook I didn't want to add. Remove unneeded commented code. --------- Co-authored-by: Caladius Co-authored-by: Garrett Cox --- mm/2s2h/BenGui/BenGui.cpp | 31 +- mm/2s2h/BenGui/BenGui.hpp | 2 + mm/2s2h/BenGui/BenInputEditorWindow.cpp | 1671 ++++++++++++++++ mm/2s2h/BenGui/BenInputEditorWindow.h | 90 + mm/2s2h/BenGui/BenMenuBar.cpp | 6 +- mm/2s2h/BenGui/BenMenuBar.h | 1 + mm/2s2h/BenGui/HudEditor.cpp | 12 +- mm/2s2h/BenGui/Menu.cpp | 471 +++++ mm/2s2h/BenGui/Menu.h | 31 + mm/2s2h/BenGui/SearchableMenuItems.h | 1705 +++++++++++++++++ mm/2s2h/BenGui/UIWidgets.cpp | 48 +- mm/2s2h/BenGui/UIWidgets.hpp | 3 + mm/2s2h/BenPort.cpp | 46 +- mm/2s2h/BenPort.h | 8 + mm/2s2h/DeveloperTools/ActorViewer.cpp | 7 - mm/2s2h/DeveloperTools/CollisionViewer.cpp | 8 - mm/2s2h/DeveloperTools/DeveloperTools.h | 2 + mm/2s2h/DeveloperTools/EventLog.cpp | 8 - mm/2s2h/DeveloperTools/SaveEditor.cpp | 8 - mm/2s2h/DeveloperTools/WarpPoint.cpp | 53 +- .../custom/fonts/Inconsolata-Regular.ttf | Bin 0 -> 101752 bytes mm/assets/custom/fonts/Montserrat-Regular.ttf | Bin 0 -> 197624 bytes 22 files changed, 4123 insertions(+), 88 deletions(-) create mode 100644 mm/2s2h/BenGui/BenInputEditorWindow.cpp create mode 100644 mm/2s2h/BenGui/BenInputEditorWindow.h create mode 100644 mm/2s2h/BenGui/Menu.cpp create mode 100644 mm/2s2h/BenGui/Menu.h create mode 100644 mm/2s2h/BenGui/SearchableMenuItems.h create mode 100644 mm/assets/custom/fonts/Inconsolata-Regular.ttf create mode 100644 mm/assets/custom/fonts/Montserrat-Regular.ttf diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index b41e537e0..dfc57bf34 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -27,7 +27,6 @@ std::shared_ptr mBenMenuBar; std::shared_ptr mConsoleWindow; std::shared_ptr mStatsWindow; -std::shared_ptr mInputEditorWindow; std::shared_ptr mGfxDebuggerWindow; std::shared_ptr mSaveEditorWindow; @@ -35,6 +34,8 @@ std::shared_ptr mHudEditorWindow; std::shared_ptr mActorViewerWindow; std::shared_ptr mCollisionViewerWindow; std::shared_ptr mEventLogWindow; +std::shared_ptr mBenMenu; +std::shared_ptr mBenInputEditorWindow; void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); @@ -47,7 +48,7 @@ void SetupGuiElements() { mBenMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); gui->SetMenuBar(std::reinterpret_pointer_cast(mBenMenuBar)); - if (gui->GetMenuBar() && !gui->GetMenuBar()->IsVisible()) { + if (!gui->GetMenuBar() && !CVarGetInteger("gSettings.DisableMenuShortcutNotify", 0)) { #if defined(__SWITCH__) || defined(__WIIU__) gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu"); #else @@ -55,6 +56,9 @@ void SetupGuiElements() { #endif } + mBenMenu = std::make_shared("gWindows.Menu", "Settings Menu"); + gui->SetMenu(mBenMenu); + mStatsWindow = gui->GetGuiWindow("Stats"); if (mStatsWindow == nullptr) { SPDLOG_ERROR("Could not find stats window"); @@ -65,37 +69,38 @@ void SetupGuiElements() { SPDLOG_ERROR("Could not find console window"); } - mInputEditorWindow = gui->GetGuiWindow("Input Editor"); - if (mInputEditorWindow == nullptr) { - SPDLOG_ERROR("Could not find input editor window"); - } - mGfxDebuggerWindow = gui->GetGuiWindow("GfxDebuggerWindow"); if (mGfxDebuggerWindow == nullptr) { SPDLOG_ERROR("Could not find input GfxDebuggerWindow"); } - mSaveEditorWindow = std::make_shared("gWindows.SaveEditor", "Save Editor"); + mBenInputEditorWindow = std::make_shared("gWindows.BenInputEditor", "2S2H Input Editor"); + gui->AddGuiWindow(mBenInputEditorWindow); + + mSaveEditorWindow = std::make_shared("gWindows.SaveEditor", "Save Editor", ImVec2(480, 600)); gui->AddGuiWindow(mSaveEditorWindow); - mHudEditorWindow = std::make_shared("gWindows.HudEditor", "Hud Editor"); + mHudEditorWindow = std::make_shared("gWindows.HudEditor", "HUD Editor", ImVec2(480, 600)); gui->AddGuiWindow(mHudEditorWindow); - mActorViewerWindow = std::make_shared("gWindows.ActorViewer", "Actor Viewer"); + mActorViewerWindow = std::make_shared("gWindows.ActorViewer", "Actor Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mActorViewerWindow); - mCollisionViewerWindow = std::make_shared("gWindows.CollisionViewer", "Collision Viewer"); + mCollisionViewerWindow = + std::make_shared("gWindows.CollisionViewer", "Collision Viewer", ImVec2(390, 475)); gui->AddGuiWindow(mCollisionViewerWindow); - mEventLogWindow = std::make_shared("gWindows.EventLog", "Event Log"); + mEventLogWindow = std::make_shared("gWindows.EventLog", "Event Log", ImVec2(520, 600)); gui->AddGuiWindow(mEventLogWindow); + gui->SetPadBtnTogglesMenu(); } void Destroy() { mBenMenuBar = nullptr; + mBenMenu = nullptr; mStatsWindow = nullptr; mConsoleWindow = nullptr; - mInputEditorWindow = nullptr; + mBenInputEditorWindow = nullptr; mGfxDebuggerWindow = nullptr; mCollisionViewerWindow = nullptr; mEventLogWindow = nullptr; diff --git a/mm/2s2h/BenGui/BenGui.hpp b/mm/2s2h/BenGui/BenGui.hpp index e5017c217..b292b9c3d 100644 --- a/mm/2s2h/BenGui/BenGui.hpp +++ b/mm/2s2h/BenGui/BenGui.hpp @@ -7,6 +7,8 @@ #include "DeveloperTools/ActorViewer.h" #include "DeveloperTools/CollisionViewer.h" #include "DeveloperTools/EventLog.h" +#include "BenInputEditorWindow.h" +#include "Menu.h" namespace BenGui { void SetupHooks(); diff --git a/mm/2s2h/BenGui/BenInputEditorWindow.cpp b/mm/2s2h/BenGui/BenInputEditorWindow.cpp new file mode 100644 index 000000000..8e3b4334e --- /dev/null +++ b/mm/2s2h/BenGui/BenInputEditorWindow.cpp @@ -0,0 +1,1671 @@ +#include "BenInputEditorWindow.h" +#include "Context.h" +#include "Gui.h" +#include "utils/StringHelper.h" +#include "public/bridge/consolevariablebridge.h" +#include "controller/controldevice/controller/mapping/sdl/SDLAxisDirectionToButtonMapping.h" + +#define SCALE_IMGUI_SIZE(value) ((value / 13.0f) * ImGui::GetFontSize()) + +BenInputEditorWindow::~BenInputEditorWindow() { + SPDLOG_TRACE("destruct input editor window"); +} + +void BenInputEditorWindow::InitElement() { + mGameInputBlockTimer = INT32_MAX; + mMappingInputBlockTimer = INT32_MAX; + mRumbleTimer = INT32_MAX; + mRumbleMappingToTest = nullptr; + mInputEditorPopupOpen = false; + + mButtonsBitmasks = { BTN_A, BTN_B, BTN_START, BTN_L, BTN_R, BTN_Z, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT }; + mDpadBitmasks = { BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; + + mDeviceIndexVisiblity.clear(); + mDeviceIndexVisiblity[Ship::ShipDeviceIndex::Keyboard] = true; + mDeviceIndexVisiblity[Ship::ShipDeviceIndex::Blue] = true; + for (auto index = 1; index < Ship::ShipDeviceIndex::Max; index++) { + mDeviceIndexVisiblity[static_cast(index)] = false; + } +} + +#define INPUT_EDITOR_WINDOW_GAME_INPUT_BLOCK_ID 95237929 +void BenInputEditorWindow::UpdateElement() { + if (mRumbleTimer != INT32_MAX) { + mRumbleTimer--; + if (mRumbleMappingToTest != nullptr) { + mRumbleMappingToTest->StartRumble(); + } + if (mRumbleTimer <= 0) { + if (mRumbleMappingToTest != nullptr) { + mRumbleMappingToTest->StopRumble(); + } + mRumbleTimer = INT32_MAX; + mRumbleMappingToTest = nullptr; + } + } + + if (mInputEditorPopupOpen && ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId)) { + Ship::Context::GetInstance()->GetControlDeck()->BlockGameInput(INPUT_EDITOR_WINDOW_GAME_INPUT_BLOCK_ID); + + // continue to block input for a third of a second after getting the mapping + mGameInputBlockTimer = ImGui::GetIO().Framerate / 3; + + if (mMappingInputBlockTimer != INT32_MAX) { + mMappingInputBlockTimer--; + if (mMappingInputBlockTimer <= 0) { + mMappingInputBlockTimer = INT32_MAX; + } + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->BlockImGuiGamepadNavigation(); + } else { + if (mGameInputBlockTimer != INT32_MAX) { + mGameInputBlockTimer--; + if (mGameInputBlockTimer <= 0) { + Ship::Context::GetInstance()->GetControlDeck()->UnblockGameInput( + INPUT_EDITOR_WINDOW_GAME_INPUT_BLOCK_ID); + mGameInputBlockTimer = INT32_MAX; + } + } + + if (Ship::Context::GetInstance()->GetWindow()->GetGui()->ImGuiGamepadNavigationEnabled()) { + mMappingInputBlockTimer = ImGui::GetIO().Framerate / 3; + } else { + mMappingInputBlockTimer = INT32_MAX; + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->UnblockImGuiGamepadNavigation(); + } +} + +void BenInputEditorWindow::DrawAnalogPreview(const char* label, ImVec2 stick, float deadzone, bool gyro) { + ImGui::BeginChild(label, ImVec2(gyro ? SCALE_IMGUI_SIZE(78) : SCALE_IMGUI_SIZE(96), SCALE_IMGUI_SIZE(85)), false); + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + gyro ? SCALE_IMGUI_SIZE(10) : SCALE_IMGUI_SIZE(18), + ImGui::GetCursorPos().y + gyro ? SCALE_IMGUI_SIZE(10) : 0)); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + const ImVec2 cursorScreenPosition = ImGui::GetCursorScreenPos(); + + // Draw the border box + float borderSquareLeft = cursorScreenPosition.x + SCALE_IMGUI_SIZE(2.0f); + float borderSquareTop = cursorScreenPosition.y + SCALE_IMGUI_SIZE(2.0f); + float borderSquareSize = SCALE_IMGUI_SIZE(65.0f); + drawList->AddRect(ImVec2(borderSquareLeft, borderSquareTop), + ImVec2(borderSquareLeft + borderSquareSize, borderSquareTop + borderSquareSize), + ImColor(100, 100, 100, 255), 0.0f, 0, 1.5f); + + // Draw the gate background + float cardinalRadius = SCALE_IMGUI_SIZE(22.5f); + float diagonalRadius = SCALE_IMGUI_SIZE(22.5f * (69.0f / 85.0f)); + + ImVec2 joystickCenterpoint = ImVec2(cursorScreenPosition.x + cardinalRadius + SCALE_IMGUI_SIZE(12), + cursorScreenPosition.y + cardinalRadius + SCALE_IMGUI_SIZE(11)); + drawList->AddQuadFilled(joystickCenterpoint, + ImVec2(joystickCenterpoint.x - diagonalRadius, joystickCenterpoint.y + diagonalRadius), + ImVec2(joystickCenterpoint.x, joystickCenterpoint.y + cardinalRadius), + ImVec2(joystickCenterpoint.x + diagonalRadius, joystickCenterpoint.y + diagonalRadius), + ImColor(130, 130, 130, 255)); + drawList->AddQuadFilled(joystickCenterpoint, + ImVec2(joystickCenterpoint.x + diagonalRadius, joystickCenterpoint.y + diagonalRadius), + ImVec2(joystickCenterpoint.x + cardinalRadius, joystickCenterpoint.y), + ImVec2(joystickCenterpoint.x + diagonalRadius, joystickCenterpoint.y - diagonalRadius), + ImColor(130, 130, 130, 255)); + drawList->AddQuadFilled(joystickCenterpoint, + ImVec2(joystickCenterpoint.x + diagonalRadius, joystickCenterpoint.y - diagonalRadius), + ImVec2(joystickCenterpoint.x, joystickCenterpoint.y - cardinalRadius), + ImVec2(joystickCenterpoint.x - diagonalRadius, joystickCenterpoint.y - diagonalRadius), + ImColor(130, 130, 130, 255)); + drawList->AddQuadFilled(joystickCenterpoint, + ImVec2(joystickCenterpoint.x - diagonalRadius, joystickCenterpoint.y - diagonalRadius), + ImVec2(joystickCenterpoint.x - cardinalRadius, joystickCenterpoint.y), + ImVec2(joystickCenterpoint.x - diagonalRadius, joystickCenterpoint.y + diagonalRadius), + ImColor(130, 130, 130, 255)); + + // Draw the joystick position indicator + ImVec2 joystickIndicatorDistanceFromCenter = ImVec2(0, 0); + if ((stick.x * stick.x + stick.y * stick.y) > (deadzone * deadzone)) { + joystickIndicatorDistanceFromCenter = + ImVec2((stick.x * (cardinalRadius / 85.0f)), -(stick.y * (cardinalRadius / 85.0f))); + } + float indicatorRadius = SCALE_IMGUI_SIZE(5.0f); + drawList->AddCircleFilled(ImVec2(joystickCenterpoint.x + joystickIndicatorDistanceFromCenter.x, + joystickCenterpoint.y + joystickIndicatorDistanceFromCenter.y), + indicatorRadius, ImColor(34, 51, 76, 255), 7); + + if (!gyro) { + ImGui::SetCursorPos( + ImVec2(ImGui::GetCursorPos().x - SCALE_IMGUI_SIZE(8), ImGui::GetCursorPos().y + SCALE_IMGUI_SIZE(72))); + ImGui::Text("X:%3d, Y:%3d", static_cast(stick.x), static_cast(stick.y)); + } + ImGui::EndChild(); +} + +#define CHIP_COLOR_N64_GREY ImVec4(0.4f, 0.4f, 0.4f, 1.0f) +#define CHIP_COLOR_N64_BLUE ImVec4(0.176f, 0.176f, 0.5f, 1.0f) +#define CHIP_COLOR_N64_GREEN ImVec4(0.0f, 0.294f, 0.0f, 1.0f) +#define CHIP_COLOR_N64_YELLOW ImVec4(0.5f, 0.314f, 0.0f, 1.0f) +#define CHIP_COLOR_N64_RED ImVec4(0.392f, 0.0f, 0.0f, 1.0f) + +#define BUTTON_COLOR_KEYBOARD_BEIGE ImVec4(0.651f, 0.482f, 0.357f, 0.5f) +#define BUTTON_COLOR_KEYBOARD_BEIGE_HOVERED ImVec4(0.651f, 0.482f, 0.357f, 1.0f) + +#define BUTTON_COLOR_GAMEPAD_BLUE ImVec4(0.0f, 0.255f, 0.976f, 0.5f) +#define BUTTON_COLOR_GAMEPAD_BLUE_HOVERED ImVec4(0.0f, 0.255f, 0.976f, 1.0f) + +#define BUTTON_COLOR_GAMEPAD_RED ImVec4(0.976f, 0.0f, 0.094f, 0.5f) +#define BUTTON_COLOR_GAMEPAD_RED_HOVERED ImVec4(0.976f, 0.0f, 0.094f, 1.0f) + +#define BUTTON_COLOR_GAMEPAD_ORANGE ImVec4(0.976f, 0.376f, 0.0f, 0.5f) +#define BUTTON_COLOR_GAMEPAD_ORANGE_HOVERED ImVec4(0.976f, 0.376f, 0.0f, 1.0f) + +#define BUTTON_COLOR_GAMEPAD_GREEN ImVec4(0.0f, 0.5f, 0.0f, 0.5f) +#define BUTTON_COLOR_GAMEPAD_GREEN_HOVERED ImVec4(0.0f, 0.5f, 0.0f, 1.0f) + +#define BUTTON_COLOR_GAMEPAD_PURPLE ImVec4(0.431f, 0.369f, 0.706f, 0.5f) +#define BUTTON_COLOR_GAMEPAD_PURPLE_HOVERED ImVec4(0.431f, 0.369f, 0.706f, 1.0f) + +void BenInputEditorWindow::GetButtonColorsForShipDeviceIndex(Ship::ShipDeviceIndex lusIndex, ImVec4& buttonColor, + ImVec4& buttonHoveredColor) { + switch (lusIndex) { + case Ship::ShipDeviceIndex::Keyboard: + buttonColor = BUTTON_COLOR_KEYBOARD_BEIGE; + buttonHoveredColor = BUTTON_COLOR_KEYBOARD_BEIGE_HOVERED; + break; + case Ship::ShipDeviceIndex::Blue: + buttonColor = BUTTON_COLOR_GAMEPAD_BLUE; + buttonHoveredColor = BUTTON_COLOR_GAMEPAD_BLUE_HOVERED; + break; + case Ship::ShipDeviceIndex::Red: + buttonColor = BUTTON_COLOR_GAMEPAD_RED; + buttonHoveredColor = BUTTON_COLOR_GAMEPAD_RED_HOVERED; + break; + case Ship::ShipDeviceIndex::Orange: + buttonColor = BUTTON_COLOR_GAMEPAD_ORANGE; + buttonHoveredColor = BUTTON_COLOR_GAMEPAD_ORANGE_HOVERED; + break; + case Ship::ShipDeviceIndex::Green: + buttonColor = BUTTON_COLOR_GAMEPAD_GREEN; + buttonHoveredColor = BUTTON_COLOR_GAMEPAD_GREEN_HOVERED; + break; + default: + buttonColor = BUTTON_COLOR_GAMEPAD_PURPLE; + buttonHoveredColor = BUTTON_COLOR_GAMEPAD_PURPLE_HOVERED; + } +} + +void BenInputEditorWindow::DrawInputChip(const char* buttonName, ImVec4 color = CHIP_COLOR_N64_GREY) { + ImGui::BeginDisabled(); + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::Button(buttonName, ImVec2(SCALE_IMGUI_SIZE(50.0f), 0)); + ImGui::PopStyleColor(); + ImGui::EndDisabled(); +} + +void BenInputEditorWindow::DrawButtonLineAddMappingButton(uint8_t port, CONTROLLERBUTTONS_T bitmask) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("addButtonMappingPopup##%d-%d", port, bitmask); + if (ImGui::Button(StringHelper::Sprintf("%s###addButtonMappingButton%d-%d", ICON_FA_PLUS, port, bitmask).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), 0.0f))) { + ImGui::OpenPopup(popupId.c_str()); + }; + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button,\nmove any axis,\nor press any key\nto add mapping"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + // todo: figure out why optional params (using id = "" in the definition) wasn't working + if (mMappingInputBlockTimer == INT32_MAX && Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetButton(bitmask) + ->AddOrEditButtonMappingFromRawPress(bitmask, "")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawButtonLineEditMappingButton(uint8_t port, CONTROLLERBUTTONS_T bitmask, std::string id) { + auto mapping = Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetButton(bitmask) + ->GetButtonMappingById(id); + if (mapping == nullptr) { + return; + } + if (!mDeviceIndexVisiblity[mapping->GetShipDeviceIndex()]) { + return; + } + + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + std::string icon = ""; + switch (mapping->GetMappingType()) { + case MAPPING_TYPE_GAMEPAD: + icon = ICON_FA_GAMEPAD; + break; + case MAPPING_TYPE_KEYBOARD: + icon = ICON_FA_KEYBOARD_O; + break; + case MAPPING_TYPE_UNKNOWN: + icon = ICON_FA_BUG; + break; + } + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + auto physicalInputDisplayName = + StringHelper::Sprintf("%s %s", icon.c_str(), mapping->GetPhysicalInputName().c_str()); + GetButtonColorsForShipDeviceIndex(mapping->GetShipDeviceIndex(), buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + auto popupId = StringHelper::Sprintf("editButtonMappingPopup##%s", id.c_str()); + if (ImGui::Button( + StringHelper::Sprintf("%s###editButtonMappingButton%s", physicalInputDisplayName.c_str(), id.c_str()) + .c_str(), + ImVec2(ImGui::CalcTextSize(physicalInputDisplayName.c_str()).x + SCALE_IMGUI_SIZE(12.0f), 0.0f))) { + ImGui::OpenPopup(popupId.c_str()); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) { + ImGui::SetTooltip(mapping->GetPhysicalDeviceName().c_str()); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button,\nmove any axis,\nor press any key\nto edit mapping"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + if (mMappingInputBlockTimer == INT32_MAX && Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetButton(bitmask) + ->AddOrEditButtonMappingFromRawPress(bitmask, id)) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(); + ImGui::SameLine(0, 0); + + auto sdlAxisDirectionToButtonMapping = std::dynamic_pointer_cast(mapping); + auto indexMapping = Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetDeviceIndexMappingFromShipDeviceIndex(mapping->GetShipDeviceIndex()); + auto sdlIndexMapping = std::dynamic_pointer_cast(indexMapping); + + if (sdlIndexMapping != nullptr && sdlAxisDirectionToButtonMapping != nullptr) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(mapping->GetShipDeviceIndex(), buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("editAxisThresholdPopup##%s", id.c_str()); + if (ImGui::Button(StringHelper::Sprintf("%s###editAxisThresholdButton%s", ICON_FA_COG, id.c_str()).c_str(), + ImVec2(ImGui::CalcTextSize(ICON_FA_COG).x + SCALE_IMGUI_SIZE(10.0f), 0.0f))) { + ImGui::OpenPopup(popupId.c_str()); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) { + ImGui::SetTooltip("Edit axis threshold"); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Axis Threshold\n\nThe extent to which the joystick\nmust be moved or the trigger\npressed to " + "initiate the assigned\nbutton action.\n\n"); + + if (sdlAxisDirectionToButtonMapping->AxisIsStick()) { + ImGui::Text("Stick axis threshold:"); + + int32_t stickAxisThreshold = sdlIndexMapping->GetStickAxisThresholdPercentage(); + if (stickAxisThreshold == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Stick Axis Threshold%s", id.c_str()).c_str())) { + sdlIndexMapping->SetStickAxisThresholdPercentage(stickAxisThreshold - 1); + sdlIndexMapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (stickAxisThreshold == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Stick Axis Threshold%s", id.c_str()).c_str(), + &stickAxisThreshold, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + sdlIndexMapping->SetStickAxisThresholdPercentage(stickAxisThreshold); + sdlIndexMapping->SaveToConfig(); + } + ImGui::SameLine(0.0f, 0.0f); + if (stickAxisThreshold == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Stick Axis Threshold%s", id.c_str()).c_str())) { + sdlIndexMapping->SetStickAxisThresholdPercentage(stickAxisThreshold + 1); + sdlIndexMapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (stickAxisThreshold == 100) { + ImGui::EndDisabled(); + } + } + + if (sdlAxisDirectionToButtonMapping->AxisIsTrigger()) { + ImGui::Text("Trigger axis threshold:"); + + int32_t triggerAxisThreshold = sdlIndexMapping->GetTriggerAxisThresholdPercentage(); + if (triggerAxisThreshold == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Trigger Axis Threshold%s", id.c_str()).c_str())) { + sdlIndexMapping->SetTriggerAxisThresholdPercentage(triggerAxisThreshold - 1); + sdlIndexMapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (triggerAxisThreshold == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Trigger Axis Threshold%s", id.c_str()).c_str(), + &triggerAxisThreshold, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + sdlIndexMapping->SetTriggerAxisThresholdPercentage(triggerAxisThreshold); + sdlIndexMapping->SaveToConfig(); + } + ImGui::SameLine(0.0f, 0.0f); + if (triggerAxisThreshold == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Trigger Axis Threshold%s", id.c_str()).c_str())) { + sdlIndexMapping->SetTriggerAxisThresholdPercentage(triggerAxisThreshold + 1); + sdlIndexMapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (triggerAxisThreshold == 100) { + ImGui::EndDisabled(); + } + } + + if (ImGui::Button("Close")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(); + ImGui::SameLine(0, 0); + } + + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + if (ImGui::Button(StringHelper::Sprintf("%s###removeButtonMappingButton%s", ICON_FA_TIMES, id.c_str()).c_str(), + ImVec2(ImGui::CalcTextSize(ICON_FA_TIMES).x + SCALE_IMGUI_SIZE(10.0f), 0.0f))) { + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetButton(bitmask) + ->ClearButtonMapping(id); + }; + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + ImGui::SameLine(0, SCALE_IMGUI_SIZE(4.0f)); +} + +void BenInputEditorWindow::DrawButtonLine(const char* buttonName, uint8_t port, CONTROLLERBUTTONS_T bitmask, + ImVec4 color = CHIP_COLOR_N64_GREY) { + ImGui::NewLine(); + ImGui::SameLine(SCALE_IMGUI_SIZE(32.0f)); + DrawInputChip(buttonName, color); + ImGui::SameLine(SCALE_IMGUI_SIZE(86.0f)); + for (auto id : mBitmaskToMappingIds[port][bitmask]) { + DrawButtonLineEditMappingButton(port, bitmask, id); + } + DrawButtonLineAddMappingButton(port, bitmask); +} + +void BenInputEditorWindow::DrawStickDirectionLineAddMappingButton(uint8_t port, uint8_t stick, + Ship::Direction direction) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("addStickDirectionMappingPopup##%d-%d-%d", port, stick, direction); + if (ImGui::Button( + StringHelper::Sprintf("%s###addStickDirectionMappingButton%d-%d-%d", ICON_FA_PLUS, port, stick, direction) + .c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), 0.0f))) { + ImGui::OpenPopup(popupId.c_str()); + }; + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button,\nmove any axis,\nor press any key\nto add mapping"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + if (stick == Ship::LEFT) { + if (mMappingInputBlockTimer == INT32_MAX && + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetLeftStick() + ->AddOrEditAxisDirectionMappingFromRawPress(direction, "")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + } else { + if (mMappingInputBlockTimer == INT32_MAX && + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRightStick() + ->AddOrEditAxisDirectionMappingFromRawPress(direction, "")) { + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawStickDirectionLineEditMappingButton(uint8_t port, uint8_t stick, + Ship::Direction direction, std::string id) { + std::shared_ptr mapping = nullptr; + if (stick == Ship::LEFT) { + mapping = Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetLeftStick() + ->GetAxisDirectionMappingById(direction, id); + } else { + mapping = Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRightStick() + ->GetAxisDirectionMappingById(direction, id); + } + + if (mapping == nullptr) { + return; + } + if (!mDeviceIndexVisiblity[mapping->GetShipDeviceIndex()]) { + return; + } + + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + std::string icon = ""; + switch (mapping->GetMappingType()) { + case MAPPING_TYPE_GAMEPAD: + icon = ICON_FA_GAMEPAD; + break; + case MAPPING_TYPE_KEYBOARD: + icon = ICON_FA_KEYBOARD_O; + break; + case MAPPING_TYPE_UNKNOWN: + icon = ICON_FA_BUG; + break; + } + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + auto physicalInputDisplayName = + StringHelper::Sprintf("%s %s", icon.c_str(), mapping->GetPhysicalInputName().c_str()); + GetButtonColorsForShipDeviceIndex(mapping->GetShipDeviceIndex(), buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + auto popupId = StringHelper::Sprintf("editStickDirectionMappingPopup##%s", id.c_str()); + if (ImGui::Button( + StringHelper::Sprintf("%s###editStickDirectionMappingButton%s", physicalInputDisplayName.c_str(), + id.c_str()) + .c_str(), + ImVec2(ImGui::CalcTextSize(physicalInputDisplayName.c_str()).x + SCALE_IMGUI_SIZE(12.0f), 0.0f))) { + ImGui::OpenPopup(popupId.c_str()); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) { + ImGui::SetTooltip(mapping->GetPhysicalDeviceName().c_str()); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button,\nmove any axis,\nor press any key\nto edit mapping"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + + if (stick == Ship::LEFT) { + if (mMappingInputBlockTimer == INT32_MAX && + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetLeftStick() + ->AddOrEditAxisDirectionMappingFromRawPress(direction, id)) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + } else { + if (mMappingInputBlockTimer == INT32_MAX && + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRightStick() + ->AddOrEditAxisDirectionMappingFromRawPress(direction, id)) { + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(); + ImGui::SameLine(0, 0); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + if (ImGui::Button( + StringHelper::Sprintf("%s###removeStickDirectionMappingButton%s", ICON_FA_TIMES, id.c_str()).c_str(), + ImVec2(ImGui::CalcTextSize(ICON_FA_TIMES).x + SCALE_IMGUI_SIZE(10.0f), 0.0f))) { + if (stick == Ship::LEFT) { + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetLeftStick() + ->ClearAxisDirectionMapping(direction, id); + } else { + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRightStick() + ->ClearAxisDirectionMapping(direction, id); + } + }; + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + ImGui::SameLine(0, SCALE_IMGUI_SIZE(4.0f)); +} + +void BenInputEditorWindow::DrawStickDirectionLine(const char* axisDirectionName, uint8_t port, uint8_t stick, + Ship::Direction direction, ImVec4 color = CHIP_COLOR_N64_GREY) { + ImGui::NewLine(); + ImGui::SameLine(); + ImGui::BeginDisabled(); + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::Button(axisDirectionName, ImVec2(SCALE_IMGUI_SIZE(26.0f), 0)); + ImGui::PopStyleColor(); + ImGui::EndDisabled(); + ImGui::SameLine(0.0f, SCALE_IMGUI_SIZE(4.0f)); + for (auto id : mStickDirectionToMappingIds[port][stick][direction]) { + DrawStickDirectionLineEditMappingButton(port, stick, direction, id); + } + DrawStickDirectionLineAddMappingButton(port, stick, direction); +} + +void BenInputEditorWindow::DrawStickSection(uint8_t port, uint8_t stick, int32_t id, + ImVec4 color = CHIP_COLOR_N64_GREY) { + static int8_t sX, sY; + std::shared_ptr controllerStick = nullptr; + if (stick == Ship::LEFT) { + controllerStick = Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLeftStick(); + } else { + controllerStick = Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetRightStick(); + } + controllerStick->Process(sX, sY); + DrawAnalogPreview(StringHelper::Sprintf("##AnalogPreview%d", id).c_str(), ImVec2(sX, sY)); + + ImGui::SameLine(); + ImGui::BeginGroup(); + DrawStickDirectionLine(ICON_FA_ARROW_UP, port, stick, Ship::UP, color); + DrawStickDirectionLine(ICON_FA_ARROW_DOWN, port, stick, Ship::DOWN, color); + DrawStickDirectionLine(ICON_FA_ARROW_LEFT, port, stick, Ship::LEFT, color); + DrawStickDirectionLine(ICON_FA_ARROW_RIGHT, port, stick, Ship::RIGHT, color); + ImGui::EndGroup(); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::TreeNode(StringHelper::Sprintf("Analog Stick Options##%d", id).c_str())) { + ImGui::Text("Sensitivity:"); + + int32_t sensitivityPercentage = controllerStick->GetSensitivityPercentage(); + if (sensitivityPercentage == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Sensitivity%d", id).c_str())) { + controllerStick->SetSensitivity(sensitivityPercentage - 1); + } + ImGui::PopButtonRepeat(); + if (sensitivityPercentage == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Sensitivity%d", id).c_str(), &sensitivityPercentage, 0, 200, + "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + controllerStick->SetSensitivity(sensitivityPercentage); + } + ImGui::SameLine(0.0f, 0.0f); + if (sensitivityPercentage == 200) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Sensitivity%d", id).c_str())) { + controllerStick->SetSensitivity(sensitivityPercentage + 1); + } + ImGui::PopButtonRepeat(); + if (sensitivityPercentage == 200) { + ImGui::EndDisabled(); + } + if (!controllerStick->SensitivityIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetStickSensitivity%d", id).c_str())) { + controllerStick->ResetSensitivityToDefault(); + } + } + + ImGui::Text("Deadzone:"); + + int32_t deadzonePercentage = controllerStick->GetDeadzonePercentage(); + if (deadzonePercentage == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Deadzone%d", id).c_str())) { + controllerStick->SetDeadzone(deadzonePercentage - 1); + } + ImGui::PopButtonRepeat(); + if (deadzonePercentage == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Deadzone%d", id).c_str(), &deadzonePercentage, 0, 100, "%d%%", + ImGuiSliderFlags_AlwaysClamp)) { + controllerStick->SetDeadzone(deadzonePercentage); + } + ImGui::SameLine(0.0f, 0.0f); + if (deadzonePercentage == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Deadzone%d", id).c_str())) { + controllerStick->SetDeadzone(deadzonePercentage + 1); + } + ImGui::PopButtonRepeat(); + if (deadzonePercentage == 100) { + ImGui::EndDisabled(); + } + if (!controllerStick->DeadzoneIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetStickDeadzone%d", id).c_str())) { + controllerStick->ResetDeadzoneToDefault(); + } + } + + ImGui::Text("Notch Snap Angle:"); + int32_t notchSnapAngle = controllerStick->GetNotchSnapAngle(); + if (notchSnapAngle == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##NotchProximityThreshold%d", id).c_str())) { + controllerStick->SetNotchSnapAngle(notchSnapAngle - 1); + } + ImGui::PopButtonRepeat(); + if (notchSnapAngle == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##NotchProximityThreshold%d", id).c_str(), ¬chSnapAngle, 0, 45, + "%d°", ImGuiSliderFlags_AlwaysClamp)) { + controllerStick->SetNotchSnapAngle(notchSnapAngle); + } + ImGui::SameLine(0.0f, 0.0f); + if (notchSnapAngle == 45) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##NotchProximityThreshold%d", id).c_str())) { + controllerStick->SetNotchSnapAngle(notchSnapAngle + 1); + } + ImGui::PopButtonRepeat(); + if (notchSnapAngle == 45) { + ImGui::EndDisabled(); + } + if (!controllerStick->NotchSnapAngleIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetStickSnap%d", id).c_str())) { + controllerStick->ResetNotchSnapAngleToDefault(); + } + } + + ImGui::TreePop(); + } +} + +void BenInputEditorWindow::UpdateBitmaskToMappingIds(uint8_t port) { + // todo: do we need this now that ControllerButton exists? + + for (auto [bitmask, button] : + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetAllButtons()) { + for (auto [id, mapping] : button->GetAllButtonMappings()) { + // using a vector here instead of a set because i want newly added mappings + // to go to the end of the list instead of autosorting + if (std::find(mBitmaskToMappingIds[port][bitmask].begin(), mBitmaskToMappingIds[port][bitmask].end(), id) == + mBitmaskToMappingIds[port][bitmask].end()) { + mBitmaskToMappingIds[port][bitmask].push_back(id); + } + } + } +} + +void BenInputEditorWindow::UpdateStickDirectionToMappingIds(uint8_t port) { + // todo: do we need this? + for (auto stick : + { std::make_pair>( + Ship::LEFT, Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLeftStick()), + std::make_pair>( + Ship::RIGHT, + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetRightStick()) }) { + for (auto direction : { Ship::LEFT, Ship::RIGHT, Ship::UP, Ship::DOWN }) { + for (auto [id, mapping] : stick.second->GetAllAxisDirectionMappingByDirection(direction)) { + // using a vector here instead of a set because i want newly added mappings + // to go to the end of the list instead of autosorting + if (std::find(mStickDirectionToMappingIds[port][stick.first][direction].begin(), + mStickDirectionToMappingIds[port][stick.first][direction].end(), + id) == mStickDirectionToMappingIds[port][stick.first][direction].end()) { + mStickDirectionToMappingIds[port][stick.first][direction].push_back(id); + } + } + } + } +} + +void BenInputEditorWindow::DrawRemoveRumbleMappingButton(uint8_t port, std::string id) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + if (ImGui::Button(StringHelper::Sprintf("%s###removeRumbleMapping%s", ICON_FA_TIMES, id.c_str()).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetRumble()->ClearRumbleMapping(id); + } + ImGui::PopStyleVar(); +} + +void BenInputEditorWindow::DrawAddRumbleMappingButton(uint8_t port) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("addRumbleMappingPopup##%d", port); + if (ImGui::Button(StringHelper::Sprintf("%s###addRumbleMapping%d", ICON_FA_PLUS, port).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + ImGui::OpenPopup(popupId.c_str()); + } + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button\nor move any axis\nto add rumble device"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + + if (mMappingInputBlockTimer == INT32_MAX && Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRumble() + ->AddRumbleMappingFromRawPress()) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +bool BenInputEditorWindow::TestingRumble() { + return mRumbleTimer != INT32_MAX; +} + +void BenInputEditorWindow::DrawRumbleSection(uint8_t port) { + for (auto [id, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetRumble() + ->GetAllRumbleMappings()) { + ImGui::AlignTextToFramePadding(); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(mapping->GetShipDeviceIndex(), buttonColor, buttonHoveredColor); + // begin hackaround https://github.com/ocornut/imgui/issues/282#issuecomment-123763192 + // spaces to have background color for text in a tree node + std::string spaces = ""; + for (size_t i = 0; i < mapping->GetPhysicalDeviceName().length(); i++) { + spaces += " "; + } + auto open = ImGui::TreeNode(StringHelper::Sprintf("%s###Rumble%s", spaces.c_str(), id.c_str()).c_str()); + ImGui::SameLine(); + ImGui::SetCursorPosX(SCALE_IMGUI_SIZE(30.0f)); + // end hackaround + + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::Button(mapping->GetPhysicalDeviceName().c_str()); + ImGui::PopStyleColor(); + ImGui::PopItemFlag(); + + DrawRemoveRumbleMappingButton(port, id); + ImGui::SameLine(); + if (ImGui::Button( + StringHelper::Sprintf("%s###rumbleTestButton%s", TestingRumble() ? "Stop" : "Test", id.c_str()) + .c_str())) { + if (mRumbleTimer != INT32_MAX) { + mRumbleTimer = INT32_MAX; + mRumbleMappingToTest->StopRumble(); + mRumbleMappingToTest = nullptr; + } else { + mRumbleTimer = ImGui::GetIO().Framerate; + mRumbleMappingToTest = mapping; + } + } + if (open) { + ImGui::Text("Small Motor Intensity:"); + + int32_t smallMotorIntensity = mapping->GetHighFrequencyIntensityPercentage(); + if (smallMotorIntensity == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Small Motor Intensity%s", id.c_str()).c_str())) { + mapping->SetHighFrequencyIntensity(smallMotorIntensity - 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (smallMotorIntensity == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Small Motor Intensity%s", id.c_str()).c_str(), + &smallMotorIntensity, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + mapping->SetHighFrequencyIntensity(smallMotorIntensity); + mapping->SaveToConfig(); + } + ImGui::SameLine(0.0f, 0.0f); + if (smallMotorIntensity == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Small Motor Intensity%s", id.c_str()).c_str())) { + mapping->SetHighFrequencyIntensity(smallMotorIntensity + 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (smallMotorIntensity == 100) { + ImGui::EndDisabled(); + } + if (!mapping->HighFrequencyIntensityIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetHighFrequencyIntensity%s", id.c_str()) + .c_str())) { + mapping->ResetHighFrequencyIntensityToDefault(); + } + } + + ImGui::Text("Large Motor Intensity:"); + + int32_t largeMotorIntensity = mapping->GetLowFrequencyIntensityPercentage(); + if (largeMotorIntensity == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##Large Motor Intensity%s", id.c_str()).c_str())) { + mapping->SetLowFrequencyIntensity(largeMotorIntensity - 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (largeMotorIntensity == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##Large Motor Intensity%s", id.c_str()).c_str(), + &largeMotorIntensity, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + mapping->SetLowFrequencyIntensity(largeMotorIntensity); + mapping->SaveToConfig(); + } + ImGui::SameLine(0.0f, 0.0f); + if (largeMotorIntensity == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##Large Motor Intensity%s", id.c_str()).c_str())) { + mapping->SetLowFrequencyIntensity(largeMotorIntensity + 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (largeMotorIntensity == 100) { + ImGui::EndDisabled(); + } + if (!mapping->LowFrequencyIntensityIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button( + StringHelper::Sprintf("Reset to Default###resetLowFrequencyIntensity%s", id.c_str()).c_str())) { + mapping->ResetLowFrequencyIntensityToDefault(); + } + } + ImGui::Dummy(ImVec2(0, SCALE_IMGUI_SIZE(20))); + + ImGui::TreePop(); + } + } + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Add rumble device"); + DrawAddRumbleMappingButton(port); +} + +void BenInputEditorWindow::DrawRemoveLEDMappingButton(uint8_t port, std::string id) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + if (ImGui::Button(StringHelper::Sprintf("%s###removeLEDMapping%s", ICON_FA_TIMES, id.c_str()).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLED()->ClearLEDMapping(id); + } + ImGui::PopStyleVar(); +} + +void BenInputEditorWindow::DrawAddLEDMappingButton(uint8_t port) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("addLEDMappingPopup##%d", port); + if (ImGui::Button(StringHelper::Sprintf("%s###addLEDMapping%d", ICON_FA_PLUS, port).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + ImGui::OpenPopup(popupId.c_str()); + } + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button\nor move any axis\nto add LED device"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + + if (mMappingInputBlockTimer == INT32_MAX && Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetLED() + ->AddLEDMappingFromRawPress()) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawLEDSection(uint8_t port) { + for (auto [id, mapping] : + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLED()->GetAllLEDMappings()) { + ImGui::AlignTextToFramePadding(); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + auto open = ImGui::TreeNode( + StringHelper::Sprintf("%s##LED%s", mapping->GetPhysicalDeviceName().c_str(), id.c_str()).c_str()); + DrawRemoveLEDMappingButton(port, id); + if (open) { + ImGui::AlignTextToFramePadding(); + ImGui::Text("LED Color:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(80.0f)); + int32_t colorSource = mapping->GetColorSource(); + if (ImGui::Combo(StringHelper::Sprintf("###ledColorSource%s", mapping->GetLEDMappingId().c_str()).c_str(), + &colorSource, "Off\0Set\0Game\0\0")) { + mapping->SetColorSource(colorSource); + }; + if (mapping->GetColorSource() == LED_COLOR_SOURCE_SET) { + ImGui::SameLine(); + ImVec4 color = { mapping->GetSavedColor().r / 255.0f, mapping->GetSavedColor().g / 255.0f, + mapping->GetSavedColor().b / 255.0f, 1.0f }; + if (ImGui::ColorEdit3( + StringHelper::Sprintf("###ledSavedColor%s", mapping->GetLEDMappingId().c_str()).c_str(), + (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) { + mapping->SetSavedColor( + Color_RGB8({ static_cast(color.x * 255.0), static_cast(color.y * 255.0), + static_cast(color.z * 255.0) })); + } + } + ImGui::TreePop(); + } + } + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Add LED device"); + DrawAddLEDMappingButton(port); +} + +void BenInputEditorWindow::DrawRemoveGyroMappingButton(uint8_t port, std::string id) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + if (ImGui::Button(StringHelper::Sprintf("%s###removeGyroMapping%s", ICON_FA_TIMES, id.c_str()).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetGyro()->ClearGyroMapping(); + } + ImGui::PopStyleVar(); +} + +void BenInputEditorWindow::DrawAddGyroMappingButton(uint8_t port) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1.0f, 0.5f)); + auto popupId = StringHelper::Sprintf("addGyroMappingPopup##%d", port); + if (ImGui::Button(StringHelper::Sprintf("%s###addGyroMapping%d", ICON_FA_PLUS, port).c_str(), + ImVec2(SCALE_IMGUI_SIZE(20.0f), SCALE_IMGUI_SIZE(20.0f)))) { + ImGui::OpenPopup(popupId.c_str()); + } + ImGui::PopStyleVar(); + + if (ImGui::BeginPopup(popupId.c_str())) { + mInputEditorPopupOpen = true; + ImGui::Text("Press any button\nor move any axis\nto add gyro device"); + if (ImGui::Button("Cancel")) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + + if (mMappingInputBlockTimer == INT32_MAX && Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(port) + ->GetGyro() + ->SetGyroMappingFromRawPress()) { + mInputEditorPopupOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawGyroSection(uint8_t port) { + auto mapping = + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetGyro()->GetGyroMapping(); + if (mapping != nullptr) { + auto id = mapping->GetGyroMappingId(); + ImGui::AlignTextToFramePadding(); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + ImGui::BulletText(mapping->GetPhysicalDeviceName().c_str()); + DrawRemoveGyroMappingButton(port, id); + + static float sPitch, sYaw = 0.0f; + mapping->UpdatePad(sPitch, sYaw); + + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x, ImGui::GetCursorPos().y - SCALE_IMGUI_SIZE(8))); + // to find a reasonable scaling factor gyro values + // I tried to find the maximum value reported by shaking + // a PS5 controller as hard as I could without worrying about breaking it + // the max I found for both pitch and yaw was ~21 + // the preview window expects values in an n64 analog stick range (-85 to 85) + // so I decided to multiply these by 85/21 + DrawAnalogPreview(StringHelper::Sprintf("###GyroPreview%s", id.c_str()).c_str(), + ImVec2(sYaw * (85.0f / 21.0f), sPitch * (85.0f / 21.0f)), 0.0f, true); + ImGui::SameLine(); + ImGui::SetCursorPos( + ImVec2(ImGui::GetCursorPos().x + SCALE_IMGUI_SIZE(8), ImGui::GetCursorPos().y + SCALE_IMGUI_SIZE(8))); + + ImGui::BeginGroup(); + ImGui::Text("Sensitivity:"); + + int32_t sensitivity = mapping->GetSensitivityPercent(); + if (sensitivity == 0) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("-##GyroSensitivity%s", id.c_str()).c_str())) { + mapping->SetSensitivity(sensitivity - 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (sensitivity == 0) { + ImGui::EndDisabled(); + } + ImGui::SameLine(0.0f, 0.0f); + ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f)); + if (ImGui::SliderInt(StringHelper::Sprintf("##GyroSensitivity%s", id.c_str()).c_str(), &sensitivity, 0, 100, + "%d%%", ImGuiSliderFlags_AlwaysClamp)) { + mapping->SetSensitivity(sensitivity); + mapping->SaveToConfig(); + } + ImGui::SameLine(0.0f, 0.0f); + if (sensitivity == 100) { + ImGui::BeginDisabled(); + } + ImGui::PushButtonRepeat(true); + if (ImGui::Button(StringHelper::Sprintf("+##GyroSensitivity%s", id.c_str()).c_str())) { + mapping->SetSensitivity(sensitivity + 1); + mapping->SaveToConfig(); + } + ImGui::PopButtonRepeat(); + if (sensitivity == 100) { + ImGui::EndDisabled(); + } + + if (!mapping->SensitivityIsDefault()) { + ImGui::SameLine(); + if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetGyroSensitivity%s", id.c_str()).c_str())) { + mapping->ResetSensitivityToDefault(); + } + } + + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + SCALE_IMGUI_SIZE(8))); + if (ImGui::Button("Recalibrate")) { + mapping->Recalibrate(); + mapping->SaveToConfig(); + } + ImGui::EndGroup(); + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x, ImGui::GetCursorPos().y - SCALE_IMGUI_SIZE(8))); + } else { + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Add gyro device"); + DrawAddGyroMappingButton(port); + } +} + +void BenInputEditorWindow::DrawButtonDeviceIcons(uint8_t portIndex, std::set bitmasks) { + std::set allLusDeviceIndices; + allLusDeviceIndices.insert(Ship::ShipDeviceIndex::Keyboard); + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappingsFromConfig()) { + allLusDeviceIndices.insert(lusIndex); + } + + std::vector> lusDeviceIndiciesWithMappings; + for (auto lusIndex : allLusDeviceIndices) { + for (auto [bitmask, button] : + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->GetAllButtons()) { + if (!bitmasks.contains(bitmask)) { + continue; + } + + if (button->HasMappingsForShipDeviceIndex(lusIndex)) { + for (auto [id, mapping] : button->GetAllButtonMappings()) { + if (mapping->GetShipDeviceIndex() == lusIndex) { + lusDeviceIndiciesWithMappings.push_back( + std::pair(lusIndex, mapping->PhysicalDeviceIsConnected())); + break; + } + } + break; + } + } + } + + for (auto [lusIndex, connected] : lusDeviceIndiciesWithMappings) { + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::SameLine(); + if (lusIndex == Ship::ShipDeviceIndex::Keyboard) { + ImGui::SmallButton(ICON_FA_KEYBOARD_O); + } else { + ImGui::SmallButton(connected ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } +} + +void BenInputEditorWindow::DrawAnalogStickDeviceIcons(uint8_t portIndex, Ship::Stick stick) { + std::set allLusDeviceIndices; + allLusDeviceIndices.insert(Ship::ShipDeviceIndex::Keyboard); + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappingsFromConfig()) { + allLusDeviceIndices.insert(lusIndex); + } + + std::vector> lusDeviceIndiciesWithMappings; + for (auto lusIndex : allLusDeviceIndices) { + auto controllerStick = + stick == Ship::Stick::LEFT_STICK + ? Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->GetLeftStick() + : Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->GetRightStick(); + if (controllerStick->HasMappingsForShipDeviceIndex(lusIndex)) { + for (auto [direction, mappings] : controllerStick->GetAllAxisDirectionMappings()) { + bool foundMapping = false; + for (auto [id, mapping] : mappings) { + if (mapping->GetShipDeviceIndex() == lusIndex) { + foundMapping = true; + lusDeviceIndiciesWithMappings.push_back( + std::pair(lusIndex, mapping->PhysicalDeviceIsConnected())); + break; + } + } + if (foundMapping) { + break; + } + } + } + } + + for (auto [lusIndex, connected] : lusDeviceIndiciesWithMappings) { + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::SameLine(); + if (lusIndex == Ship::ShipDeviceIndex::Keyboard) { + ImGui::SmallButton(ICON_FA_KEYBOARD_O); + } else { + ImGui::SmallButton(connected ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } +} + +void BenInputEditorWindow::DrawRumbleDeviceIcons(uint8_t portIndex) { + std::set allLusDeviceIndices; + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappingsFromConfig()) { + allLusDeviceIndices.insert(lusIndex); + } + + std::vector> lusDeviceIndiciesWithMappings; + for (auto lusIndex : allLusDeviceIndices) { + if (Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->GetRumble() + ->HasMappingsForShipDeviceIndex(lusIndex)) { + for (auto [id, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->GetRumble() + ->GetAllRumbleMappings()) { + if (mapping->GetShipDeviceIndex() == lusIndex) { + lusDeviceIndiciesWithMappings.push_back( + std::pair(lusIndex, mapping->PhysicalDeviceIsConnected())); + break; + } + } + } + } + + for (auto [lusIndex, connected] : lusDeviceIndiciesWithMappings) { + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::SameLine(); + ImGui::SmallButton(connected ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } +} + +void BenInputEditorWindow::DrawGyroDeviceIcons(uint8_t portIndex) { + auto mapping = + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->GetGyro()->GetGyroMapping(); + if (mapping == nullptr) { + return; + } + + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(mapping->GetShipDeviceIndex(), buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::SameLine(); + ImGui::SmallButton(mapping->PhysicalDeviceIsConnected() ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} + +void BenInputEditorWindow::DrawLEDDeviceIcons(uint8_t portIndex) { + std::set allLusDeviceIndices; + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappingsFromConfig()) { + allLusDeviceIndices.insert(lusIndex); + } + + std::vector> lusDeviceIndiciesWithMappings; + for (auto lusIndex : allLusDeviceIndices) { + if (Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->GetRumble() + ->HasMappingsForShipDeviceIndex(lusIndex)) { + for (auto [id, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->GetLED() + ->GetAllLEDMappings()) { + if (mapping->GetShipDeviceIndex() == lusIndex) { + lusDeviceIndiciesWithMappings.push_back( + std::pair(lusIndex, mapping->PhysicalDeviceIsConnected())); + break; + } + } + } + } + + for (auto [lusIndex, connected] : lusDeviceIndiciesWithMappings) { + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + ImGui::SameLine(); + ImGui::SmallButton(connected ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } +} + +void BenInputEditorWindow::DrawDeviceVisibilityButtons() { + std::map> indexMappings; + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappingsFromConfig()) { + auto sdlIndexMapping = std::static_pointer_cast(mapping); + if (sdlIndexMapping == nullptr) { + continue; + } + + indexMappings[lusIndex] = { sdlIndexMapping->GetSDLControllerName(), -1 }; + } + + for (auto [lusIndex, mapping] : + Ship::Context::GetInstance()->GetControlDeck()->GetDeviceIndexMappingManager()->GetAllDeviceIndexMappings()) { + auto sdlIndexMapping = std::static_pointer_cast(mapping); + if (sdlIndexMapping == nullptr) { + continue; + } + + indexMappings[lusIndex] = { sdlIndexMapping->GetSDLControllerName(), sdlIndexMapping->GetSDLDeviceIndex() }; + } + + auto keyboardButtonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto keyboardButtonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(Ship::ShipDeviceIndex::Keyboard, keyboardButtonColor, keyboardButtonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, keyboardButtonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, keyboardButtonHoveredColor); + bool keyboardVisible = mDeviceIndexVisiblity[Ship::ShipDeviceIndex::Keyboard]; + if (ImGui::Button(StringHelper::Sprintf("%s %s Keyboard", keyboardVisible ? ICON_FA_EYE : ICON_FA_EYE_SLASH, + ICON_FA_KEYBOARD_O) + .c_str())) { + mDeviceIndexVisiblity[Ship::ShipDeviceIndex::Keyboard] = !keyboardVisible; + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + for (auto [lusIndex, info] : indexMappings) { + auto [name, sdlIndex] = info; + bool connected = sdlIndex != -1; + + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + bool visible = mDeviceIndexVisiblity[lusIndex]; + if (ImGui::Button( + StringHelper::Sprintf("%s %s %s (%s)", visible ? ICON_FA_EYE : ICON_FA_EYE_SLASH, + connected ? ICON_FA_GAMEPAD : ICON_FA_CHAIN_BROKEN, name.c_str(), + connected ? StringHelper::Sprintf("SDL %d", sdlIndex).c_str() : "Disconnected") + .c_str())) { + mDeviceIndexVisiblity[lusIndex] = !visible; + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } +} + +void BenInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { + if (ImGui::Button("Clear All", ImGui::CalcTextSize("Clear All") * 2)) { + ImGui::OpenPopup("Clear All##clearAllPopup"); + } + if (ImGui::BeginPopupModal("Clear All##clearAllPopup", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("This will clear all mappings for port %d.\n\nContinue?", portIndex + 1); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Clear All")) { + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->ClearAllMappings(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawPortTab(uint8_t portIndex) { + if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d###port%d", portIndex + 1, portIndex).c_str())) { + DrawPortTabContents(portIndex); + ImGui::EndTabItem(); + } +} + +void BenInputEditorWindow::DrawPortTabContents(uint8_t portIndex) { + DrawClearAllButton(portIndex); + DrawSetDefaultsButton(portIndex); + if (!Ship::Context::GetInstance()->GetControlDeck()->IsSinglePlayerMappingMode()) { + ImGui::SameLine(); + if (ImGui::Button("Reorder controllers")) { + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Controller Reordering")->Show(); + } + } + DrawDeviceVisibilityButtons(); + + 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)); + + if (ImGui::CollapsingHeader("Buttons", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { + DrawButtonDeviceIcons(portIndex, mButtonsBitmasks); + DrawButtonLine("A", portIndex, BTN_A, CHIP_COLOR_N64_BLUE); + DrawButtonLine("B", portIndex, BTN_B, CHIP_COLOR_N64_GREEN); + DrawButtonLine("Start", portIndex, BTN_START, CHIP_COLOR_N64_RED); + DrawButtonLine("L", portIndex, BTN_L); + DrawButtonLine("R", portIndex, BTN_R); + DrawButtonLine("Z", portIndex, BTN_Z); + DrawButtonLine(StringHelper::Sprintf("C %s", ICON_FA_ARROW_UP).c_str(), portIndex, BTN_CUP, + CHIP_COLOR_N64_YELLOW); + DrawButtonLine(StringHelper::Sprintf("C %s", ICON_FA_ARROW_DOWN).c_str(), portIndex, BTN_CDOWN, + CHIP_COLOR_N64_YELLOW); + DrawButtonLine(StringHelper::Sprintf("C %s", ICON_FA_ARROW_LEFT).c_str(), portIndex, BTN_CLEFT, + CHIP_COLOR_N64_YELLOW); + DrawButtonLine(StringHelper::Sprintf("C %s", ICON_FA_ARROW_RIGHT).c_str(), portIndex, BTN_CRIGHT, + CHIP_COLOR_N64_YELLOW); + } else { + DrawButtonDeviceIcons(portIndex, mButtonsBitmasks); + } + + if (ImGui::CollapsingHeader("D-Pad", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { + DrawButtonDeviceIcons(portIndex, mDpadBitmasks); + DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_UP).c_str(), portIndex, BTN_DUP); + DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_DOWN).c_str(), portIndex, BTN_DDOWN); + DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_LEFT).c_str(), portIndex, BTN_DLEFT); + DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_RIGHT).c_str(), portIndex, BTN_DRIGHT); + } else { + DrawButtonDeviceIcons(portIndex, mDpadBitmasks); + } + + if (ImGui::CollapsingHeader("Analog Stick", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { + DrawAnalogStickDeviceIcons(portIndex, Ship::LEFT_STICK); + DrawStickSection(portIndex, Ship::LEFT, 0); + } else { + DrawAnalogStickDeviceIcons(portIndex, Ship::LEFT_STICK); + } + + if (ImGui::CollapsingHeader("Additional (\"Right\") Stick")) { + DrawAnalogStickDeviceIcons(portIndex, Ship::RIGHT_STICK); + DrawStickSection(portIndex, Ship::RIGHT, 1, CHIP_COLOR_N64_YELLOW); + } else { + DrawAnalogStickDeviceIcons(portIndex, Ship::RIGHT_STICK); + } + + if (ImGui::CollapsingHeader("Rumble")) { + DrawRumbleDeviceIcons(portIndex); + DrawRumbleSection(portIndex); + } else { + DrawRumbleDeviceIcons(portIndex); + } + + if (ImGui::CollapsingHeader("Gyro")) { + DrawGyroDeviceIcons(portIndex); + DrawGyroSection(portIndex); + } else { + DrawGyroDeviceIcons(portIndex); + } + + if (ImGui::CollapsingHeader("LEDs")) { + DrawLEDDeviceIcons(portIndex); + DrawLEDSection(portIndex); + } else { + DrawLEDDeviceIcons(portIndex); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} + +void BenInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { + ImGui::SameLine(); + auto popupId = StringHelper::Sprintf("setDefaultsPopup##%d", portIndex); + if (ImGui::Button(StringHelper::Sprintf("Set Defaults##%d", portIndex).c_str(), + ImVec2(ImGui::CalcTextSize("Set Defaults") * 2))) { + ImGui::OpenPopup(popupId.c_str()); + } + + if (ImGui::BeginPopup(popupId.c_str())) { + std::map> indexMappings; + for (auto [lusIndex, mapping] : Ship::Context::GetInstance() + ->GetControlDeck() + ->GetDeviceIndexMappingManager() + ->GetAllDeviceIndexMappings()) { + auto sdlIndexMapping = std::static_pointer_cast(mapping); + if (sdlIndexMapping == nullptr) { + continue; + } + + indexMappings[lusIndex] = { sdlIndexMapping->GetSDLControllerName(), sdlIndexMapping->GetSDLDeviceIndex() }; + } + + bool shouldClose = false; + ImGui::PushStyleColor(ImGuiCol_Button, BUTTON_COLOR_KEYBOARD_BEIGE); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, BUTTON_COLOR_KEYBOARD_BEIGE_HOVERED); + if (ImGui::Button(StringHelper::Sprintf("%s Keyboard", ICON_FA_KEYBOARD_O).c_str())) { + ImGui::OpenPopup("Set Defaults for Keyboard"); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + 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); + if (ImGui::Button("Cancel")) { + shouldClose = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set defaults")) { + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->ClearAllMappingsForDevice(Ship::ShipDeviceIndex::Keyboard); + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->AddDefaultMappings( + Ship::ShipDeviceIndex::Keyboard); + shouldClose = true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + for (auto [lusIndex, info] : indexMappings) { + auto [name, sdlIndex] = info; + + auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + GetButtonColorsForShipDeviceIndex(lusIndex, buttonColor, buttonHoveredColor); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + if (ImGui::Button(StringHelper::Sprintf("%s %s (%s)", ICON_FA_GAMEPAD, name.c_str(), + StringHelper::Sprintf("SDL %d", sdlIndex).c_str()) + .c_str())) { + ImGui::OpenPopup(StringHelper::Sprintf("Set Defaults for %s", name.c_str()).c_str()); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + if (ImGui::BeginPopupModal(StringHelper::Sprintf("Set Defaults for %s", name.c_str()).c_str(), NULL, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("This will clear all existing mappings for\n%s (SDL %d) on port %d.\n\nContinue?", + name.c_str(), sdlIndex, portIndex + 1); + if (ImGui::Button("Cancel")) { + shouldClose = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set defaults")) { + Ship::Context::GetInstance() + ->GetControlDeck() + ->GetControllerByPort(portIndex) + ->ClearAllMappingsForDevice(lusIndex); + Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->AddDefaultMappings( + lusIndex); + shouldClose = true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + if (ImGui::Button("Cancel") || shouldClose) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } +} + +void BenInputEditorWindow::DrawFullContents() { + ImGui::BeginTabBar("##ControllerConfigPortTabs"); + for (uint8_t i = 0; i < 4; i++) { + DrawPortTab(i); + } + ImGui::EndTabBar(); +} + +void BenInputEditorWindow::DrawElement() { + DrawFullContents(); +} diff --git a/mm/2s2h/BenGui/BenInputEditorWindow.h b/mm/2s2h/BenGui/BenInputEditorWindow.h new file mode 100644 index 000000000..adb1b8be8 --- /dev/null +++ b/mm/2s2h/BenGui/BenInputEditorWindow.h @@ -0,0 +1,90 @@ +#pragma once + +#include "stdint.h" +#include "window/gui/GuiWindow.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include +#include +#include +#include "controller/controldevice/controller/Controller.h" + +class BenInputEditorWindow : public Ship::GuiWindow { + public: + using Ship::GuiWindow::GuiWindow; + ~BenInputEditorWindow(); + + void DrawButton(const char* label, int32_t n64Btn, int32_t currentPort, int32_t* btnReading); + + void DrawInputChip(const char* buttonName, ImVec4 color); + void DrawAnalogPreview(const char* label, ImVec2 stick, float deadzone = 0, bool gyro = false); + void DrawControllerSchema(); + bool TestingRumble(); + void DrawFullContents(); + void DrawPortTabContents(uint8_t portIndex); + + protected: + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + + private: + void DrawStickDirectionLine(const char* axisDirectionName, uint8_t port, uint8_t stick, Ship::Direction direction, + ImVec4 color); + void DrawButtonLine(const char* buttonName, uint8_t port, CONTROLLERBUTTONS_T bitmask, ImVec4 color); + void DrawButtonLineEditMappingButton(uint8_t port, CONTROLLERBUTTONS_T bitmask, std::string id); + void DrawButtonLineAddMappingButton(uint8_t port, CONTROLLERBUTTONS_T bitmask); + + void DrawStickDirectionLineEditMappingButton(uint8_t port, uint8_t stick, Ship::Direction direction, + std::string id); + void DrawStickDirectionLineAddMappingButton(uint8_t port, uint8_t stick, Ship::Direction direction); + void DrawStickSection(uint8_t port, uint8_t stick, int32_t id, ImVec4 color); + + void DrawRumbleSection(uint8_t port); + void DrawRemoveRumbleMappingButton(uint8_t port, std::string id); + void DrawAddRumbleMappingButton(uint8_t port); + + void DrawLEDSection(uint8_t port); + void DrawRemoveLEDMappingButton(uint8_t port, std::string id); + void DrawAddLEDMappingButton(uint8_t port); + + void DrawGyroSection(uint8_t port); + void DrawRemoveGyroMappingButton(uint8_t port, std::string id); + void DrawAddGyroMappingButton(uint8_t port); + + int32_t mGameInputBlockTimer; + int32_t mMappingInputBlockTimer; + int32_t mRumbleTimer; + std::shared_ptr mRumbleMappingToTest; + + // mBitmaskToMappingIds[port][bitmask] = { id0, id1, ... } + std::unordered_map>> mBitmaskToMappingIds; + + // mStickDirectionToMappingIds[port][stick][direction] = { id0, id1, ... } + std::unordered_map>>> + mStickDirectionToMappingIds; + + void UpdateBitmaskToMappingIds(uint8_t port); + void UpdateStickDirectionToMappingIds(uint8_t port); + + void GetButtonColorsForShipDeviceIndex(Ship::ShipDeviceIndex lusIndex, ImVec4& buttonColor, + ImVec4& buttonHoveredColor); + void DrawPortTab(uint8_t portIndex); + std::set mButtonsBitmasks; + std::set mDpadBitmasks; + void DrawButtonDeviceIcons(uint8_t portIndex, std::set bitmasks); + void DrawAnalogStickDeviceIcons(uint8_t portIndex, Ship::Stick stick); + void DrawRumbleDeviceIcons(uint8_t portIndex); + void DrawGyroDeviceIcons(uint8_t portIndex); + void DrawLEDDeviceIcons(uint8_t portIndex); + bool mInputEditorPopupOpen; + void DrawSetDefaultsButton(uint8_t portIndex); + void DrawClearAllButton(uint8_t portIndex); + + std::map mDeviceIndexVisiblity; + void DrawDeviceVisibilityButtons(); +}; diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index 009f6c075..1a9ca3822 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -153,7 +153,7 @@ void DrawBenMenu() { } } -extern std::shared_ptr mInputEditorWindow; +extern std::shared_ptr mBenInputEditorWindow; void DrawSettingsMenu() { if (UIWidgets::BeginMenu("Settings")) { @@ -312,8 +312,8 @@ void DrawSettingsMenu() { // #region 2S2H [Todo] None of this works yet /* if (UIWidgets::BeginMenu("Controller")) { */ - if (mInputEditorWindow) { - UIWidgets::WindowButton("Controller Mapping", "gWindows.InputEditor", mInputEditorWindow); + if (mBenInputEditorWindow) { + UIWidgets::WindowButton("Controller Mapping", "gWindows.InputEditor", mBenInputEditorWindow); } /* #ifndef __SWITCH__ diff --git a/mm/2s2h/BenGui/BenMenuBar.h b/mm/2s2h/BenGui/BenMenuBar.h index 7f509f8fb..7342a0101 100644 --- a/mm/2s2h/BenGui/BenMenuBar.h +++ b/mm/2s2h/BenGui/BenMenuBar.h @@ -7,6 +7,7 @@ #include "DeveloperTools/ActorViewer.h" #include "DeveloperTools/CollisionViewer.h" #include "DeveloperTools/EventLog.h" +#include "BenInputEditorWindow.h" namespace BenGui { class BenMenuBar : public Ship::GuiMenuBar { diff --git a/mm/2s2h/BenGui/HudEditor.cpp b/mm/2s2h/BenGui/HudEditor.cpp index 95202bbbf..a14926542 100644 --- a/mm/2s2h/BenGui/HudEditor.cpp +++ b/mm/2s2h/BenGui/HudEditor.cpp @@ -167,12 +167,6 @@ enum Presets { }; void HudEditorWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(480, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Hud Editor", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - return; - } - static HudEditor::Presets preset = HudEditor::Presets::VANILLA; if (UIWidgets::Combobox("Preset", &preset, presetNames)) { for (int i = HUD_EDITOR_ELEMENT_B; i < HUD_EDITOR_ELEMENT_MAX; i++) { @@ -252,7 +246,9 @@ void HudEditorWindow::DrawElement() { } if (CVarGetInteger(hudEditorElements[i].modeCvar, HUD_EDITOR_ELEMENT_MODE_VANILLA) >= HUD_EDITOR_ELEMENT_MODE_MOVABLE_43) { - if (ImGui::BeginTable("##table", 3, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoBordersInBody)) { + if (ImGui::BeginTable("##table", 3, + ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoBordersInBody | + ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextColumn(); UIWidgets::CVarSliderInt("X", hudEditorElements[i].xCvar, -10, 330, hudEditorElements[i].defaultX, { @@ -279,6 +275,4 @@ void HudEditorWindow::DrawElement() { } ImGui::PopID(); } - - ImGui::End(); } diff --git a/mm/2s2h/BenGui/Menu.cpp b/mm/2s2h/BenGui/Menu.cpp new file mode 100644 index 000000000..c788bcc58 --- /dev/null +++ b/mm/2s2h/BenGui/Menu.cpp @@ -0,0 +1,471 @@ +#include "Menu.h" +#include "BenPort.h" +#include "BenGui.hpp" +#include "UIWidgets.hpp" +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "2s2h/Enhancements/Enhancements.h" +#include "2s2h/Enhancements/Graphics/MotionBlur.h" +#include "2s2h/Enhancements/Graphics/PlayAsKafei.h" +#include "2s2h/DeveloperTools/DeveloperTools.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include "DeveloperTools/SaveEditor.h" +#include "DeveloperTools/ActorViewer.h" +#include "DeveloperTools/CollisionViewer.h" +#include "DeveloperTools/EventLog.h" +#include "HudEditor.h" + +#include "SearchableMenuItems.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +extern PlayState* gPlayState; +} +std::vector windowTypeSizes = { {} }; + +namespace BenGui { + +extern std::shared_ptr mHudEditorWindow; +extern std::shared_ptr mStatsWindow; +extern std::shared_ptr mConsoleWindow; +extern std::shared_ptr mGfxDebuggerWindow; +extern std::shared_ptr mSaveEditorWindow; +extern std::shared_ptr mActorViewerWindow; +extern std::shared_ptr mCollisionViewerWindow; +extern std::shared_ptr mEventLogWindow; +extern std::shared_ptr mBenInputEditorWindow; + +extern std::shared_ptr> availableWindowBackends; +extern std::unordered_map availableWindowBackendsMap; +extern Ship::WindowBackend configWindowBackend; +extern void UpdateWindowBackendObjects(); + +// BENTODO: Not implemented yet +// UIWidgets::CVarCheckbox("Widescreen Actor Culling", +// "gEnhancements.Graphics.ActorCullingAccountsForWidescreen", +// { .tooltip = "Adjusts the culling planes to account for widescreen resolutions. " +// "This may have unintended side effects." }); + +// if (gPlayState != NULL) { +// ImGui::Separator(); +// SearchMenuGetItem(MENU_ITEM_FRAME_ADVANCE_ENABLE); +// if (gPlayState->frameAdvCtx.enabled) { +// SearchMenuGetItem(MENU_ITEM_FRAME_ADVANCE_SINGLE); +// SearchMenuGetItem(MENU_ITEM_FRAME_ADVANCE_HOLD); +// if (ImGui::IsItemActive()) { +// CVarSetInteger("gDeveloperTools.FrameAdvanceTick", 1); +// } +// } +// } +// ImGui::PushStyleColor(ImGuiCol_Button, menuTheme[menuThemeIndex]); +// RenderWarpPointSection(); +// ImGui::PopStyleColor(1); +//} + +BenMenu::BenMenu(const std::string& consoleVariable, const std::string& name) : GuiWindow(consoleVariable, name) { +} + +void BenMenu::InitElement() { + popped = CVarGetInteger("gSettings.Menu.Popout", 0); + poppedSize.x = CVarGetInteger("gSettings.Menu.PoppedWidth", 1280); + poppedSize.y = CVarGetInteger("gSettings.Menu.PoppedHeight", 800); + poppedPos.x = CVarGetInteger("gSettings.Menu.PoppedPos.x", 0); + poppedPos.y = CVarGetInteger("gSettings.Menu.PoppedPos.y", 0); + AddSettings(); + AddEnhancements(); + AddDevTools(); + + menuEntries = { { "Settings", settingsSidebar, "gSettings.Menu.SettingsSidebarIndex" }, + { "Enhancements", enhancementsSidebar, "gSettings.Menu.EnhancementsSidebarIndex" }, + { "Developer Tools", devToolsSidebar, "gSettings.Menu.DevToolsSidebarIndex" } }; + + UpdateWindowBackendObjects(); +} + +void BenMenu::UpdateElement() { +} + +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; +} + +void BenMenu::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void BenMenu::DrawElement() { + for (auto& [reason, info] : disabledMap) { + info.active = info.evaluation(info); + } + menuThemeIndex = static_cast(CVarGetInteger("gSettings.MenuTheme", 3)); + + windowHeight = ImGui::GetMainViewport()->WorkSize.y; + windowWidth = ImGui::GetMainViewport()->WorkSize.x; + auto windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + bool popout = CVarGetInteger("gSettings.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("gSettings.Menu.PoppedWidth", poppedSize.x); + CVarSetFloat("gSettings.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 | ImGuiWindowFlags_NoBringToFrontOnFocus)) { + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::End(); + return; + } + if (popped != popout) { + if (!popout) { + ImGui::PopStyleVar(); + } + CVarSetInteger("gSettings.Menu.Popout", popped); + CVarSetFloat("gSettings.Menu.PoppedWidth", poppedSize.x); + CVarSetFloat("gSettings.Menu.PoppedHeight", poppedSize.y); + CVarSetFloat("gSettings.Menu.PoppedPos.x", poppedSize.x); + CVarSetFloat("gSettings.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)); + auto sectionCount = menuEntries.size(); + const char* headerCvar = "gSettings.Menu.SelectedHeader"; + uint8_t headerIndex = CVarGetInteger(headerCvar, 0); + ImVec2 pos = window->DC.CursorPos; + float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (sectionCount + 1)); + std::vector headerSizes; + float headerWidth = 200.0f + style.ItemSpacing.x; + for (int i = 0; i < sectionCount; i++) { + ImVec2 size = ImGui::CalcTextSize(menuEntries.at(i).label.c_str()); + headerSizes.push_back(size); + headerWidth += size.x + style.FramePadding.x * 2; + if (i + 1 < sectionCount) { + headerWidth += style.ItemSpacing.x; + } + } + ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; + 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::vector 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; + } + if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, { .size = UIWidgets::Sizes::Inline, .tooltip = "Close Menu (Esc)" })) { + ToggleVisibility(); + } + 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("gSettings.SearchAutofocus", 0); + bool headerSearch = !CVarGetInteger("gSettings.SidebarSearch", 0); + ImGui::BeginChild("Header Selection", headerSelSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar); + for (int i = 0; i < sectionCount; i++) { + auto entry = menuEntries.at(i); + uint8_t nextIndex = i; + UIWidgets::PushStyleButton(menuTheme[menuThemeIndex]); + if (headerIndex != i) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuHeaderEntry(entry.label)) { + if (autoFocus) { + menuSearch.Clear(); + } + CVarSetInteger(headerCvar, i); + CVarSave(); + nextIndex = i; + } + if (headerIndex != i) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (headerIndex == i) { + sidebar = entry.sidebarEntries; + } + if (i + 1 < sectionCount) { + ImGui::SameLine(); + } + if (nextIndex != i) { + headerIndex = nextIndex; + } + } + std::string menuSearchText = ""; + if (headerSearch) { + ImGui::SameLine(); + if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0, 0, 0, 0 }); + menuSearch.Draw("##search", 200.0f); + menuSearchText = menuSearch.InputBuf; + 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); + if (UIWidgets::Button(ICON_FA_UNDO, { .color = UIWidgets::Colors::Red, + .size = UIWidgets::Sizes::Inline, + .tooltip = "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"); + } + ImGui::SameLine(); + if (UIWidgets::Button( + ICON_FA_POWER_OFF, + { .color = UIWidgets::Colors::Red, .size = UIWidgets::Sizes::Inline, .tooltip = "Quit 2S2H" })) { + 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 * (sectionCount + 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; + + uint8_t sectionIndex = CVarGetInteger(sidebarCvar, 0); + if (sectionIndex > sidebar.size() - 1) + sectionIndex = sidebar.size() - 1; + if (sectionIndex < 0) + sectionIndex = 0; + 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 (size_t i = 0; i < sidebar.size(); i++) { + auto sidebarEntry = sidebar.at(i); + uint8_t nextIndex = i; + UIWidgets::PushStyleButton(menuTheme[menuThemeIndex]); + if (sectionIndex != i) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuSidebarEntry(sidebarEntry.label)) { + if (autoFocus) { + menuSearch.Clear(); + } + CVarSetInteger(sidebarCvar, i); + CVarSave(); + nextIndex = i; + } + if (sectionIndex != i) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (nextIndex != i) { + sectionIndex = i; + } + } + 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 = sidebar.at(sectionIndex).label + " 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) { + ImGui::BeginChild("Search Results"); + int searchCount = 0; + for (auto& [menuLabel, menuSidebar, cvar] : menuEntries) { + for (auto& sidebar : menuSidebar) { + for (auto& widgets : sidebar.columnWidgets) { + int column = 1; + for (auto& info : widgets) { + if (info.widgetType == WIDGET_SEPARATOR || info.widgetType == WIDGET_SEPARATOR_TEXT || + info.isHidden) { + continue; + } + std::string widgetStr = std::string(info.widgetName) + std::string(info.widgetTooltip); + 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) { + SearchMenuGetItem(info); + searchCount++; + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::Colors::Gray); + std::string origin = fmt::format(" ({} -> {}, Clmn {})", menuLabel, sidebar.label, column); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + } + } + column++; + } + } + } + + 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); + if (UIWidgets::Button("Clear Search", { .size = UIWidgets::Sizes::Inline })) { + menuSearch.Clear(); + } + } + + ImGui::EndChild(); + } else { + 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& entry : sidebar.at(sectionIndex).columnWidgets.at(i)) { + SearchMenuGetItem(entry); + } + 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 BenGui diff --git a/mm/2s2h/BenGui/Menu.h b/mm/2s2h/BenGui/Menu.h new file mode 100644 index 000000000..ddfb2a57e --- /dev/null +++ b/mm/2s2h/BenGui/Menu.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "UIWidgets.hpp" + +namespace BenGui { +class BenMenu : public Ship::GuiWindow { + public: + using Ship::GuiWindow::GuiWindow; + + BenMenu(const std::string& consoleVariable, const std::string& name); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + + protected: + ImVec2 mOriginalSize; + std::string mName; + uint32_t mWindowFlags; + + private: + bool allowPopout = true; // PortNote: should be set to false on small screen ports + bool popped; + ImVec2 poppedSize; + ImVec2 poppedPos; + float windowHeight; + float windowWidth; +}; +} // namespace BenGui \ No newline at end of file diff --git a/mm/2s2h/BenGui/SearchableMenuItems.h b/mm/2s2h/BenGui/SearchableMenuItems.h new file mode 100644 index 000000000..a8d8c846f --- /dev/null +++ b/mm/2s2h/BenGui/SearchableMenuItems.h @@ -0,0 +1,1705 @@ +#include "2s2h/Enhancements/Enhancements.h" +#include "2s2h/DeveloperTools/DeveloperTools.h" +#include "2s2h/Enhancements/Graphics/3DItemDrops.h" +#include "UIWidgets.hpp" +#include "BenMenuBar.h" +#include "macros.h" +#include "variables.h" +#include +#include + +extern "C" { +#include "functions.h" +extern PlayState* gPlayState; +} + +typedef enum { + COLOR_WHITE, + COLOR_GRAY, + COLOR_DARK_GRAY, + COLOR_INDIGO, + COLOR_RED, + COLOR_DARK_RED, + COLOR_LIGHT_GREEN, + COLOR_GREEN, + COLOR_DARK_GREEN, + COLOR_YELLOW, +} ColorOption; + +typedef enum { + DISABLE_FOR_CAMERAS_OFF, + DISABLE_FOR_DEBUG_CAM_ON, + DISABLE_FOR_DEBUG_CAM_OFF, + DISABLE_FOR_FREE_LOOK_ON, + DISABLE_FOR_FREE_LOOK_OFF, + DISABLE_FOR_AUTO_SAVE_OFF, + DISABLE_FOR_NULL_PLAY_STATE, + DISABLE_FOR_DEBUG_MODE_OFF, + 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_MOTION_BLUR_MODE, + DISABLE_FOR_MOTION_BLUR_OFF, + DISABLE_FOR_FRAME_ADVANCE_OFF, + DISABLE_FOR_WARP_POINT_NOT_SET +} DisableOption; + +struct widgetInfo; +struct disabledInfo; +using VoidFunc = void (*)(); +using DisableInfoFunc = bool (*)(disabledInfo&); +using DisableVec = std::vector; +using WidgetFunc = void (*)(widgetInfo&); +std::string disabledTempTooltip; +const char* disabledTooltip; +bool disabledValue = false; +ColorOption menuThemeIndex = COLOR_INDIGO; +const ImVec4 COLOR_NONE = { 0, 0, 0, 0 }; + +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_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 because of special operations that can't be handled easily with the normal combobox + // widget + WIDGET_VIDEO_BACKEND // same as above +} WidgetType; + +typedef enum { + MOTION_BLUR_DYNAMIC, + MOTION_BLUR_ALWAYS_OFF, + MOTION_BLUR_ALWAYS_ON, +} MotionBlurOption; + +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; + +// contains various information used to display specific types of widgets +// `min` and `max` are usually only used by sliders +// `defaultVariant` can be used by all widgetTypes, but defaults to 0, "", 0.0f, and white respectively, so only needed +// when you want to change those defaults +// `comboBoxOptions` is the list of dropdown options to be added to a dropdown. +// this needs to be a map of int32_t to const char*, but can also be enum values +// `valuePointer` is used for non-CVar sliders to track current widgetValue +// `size` applies only to buttons, determines whether it stretches to fill its current container or only fits its text +// `color` can be used in any way, depending on the widget. color pickers (COLOR_24 and COLOR_32) will eventually +// use this for storing its current value; WIDGET_SEPARATOR_TEXT uses this to change the text color `windowName` +// is used when using WIDGET_WINDOW_BUTTON, which optionally draws windows, like Stats, in the menu with a button +// to pop the window out of the main menu overlay +// `windowName` is what is displayed and searched for `windowButton` type and window interactions +// `labelPosition` applies to checkbox, combobox and button types. specifies label orientation compared to widget body +// `sameLine` allows widgets to be displayed on the same line as the previously registered widget +// the next three only apply to sliders. `showButtons` shows or hides the +/- buttons on either side of a slider +// `format` specifies how info is displayed within the slider +// `isPercentage` toggles the float slider's value being multiplied by 100 to show percentages instead of direct floats +struct WidgetOptions { + CVarVariant min; + CVarVariant max; + CVarVariant defaultVariant; + std::unordered_map comboBoxOptions; + std::variant valuePointer; + ImVec2 size = UIWidgets::Sizes::Fill; + ImVec4 color = COLOR_NONE; + const char* windowName = ""; + UIWidgets::LabelPosition labelPosition = UIWidgets::LabelPosition::None; + bool sameLine = false; + bool showButtons = true; + const char* format = "%f"; + bool isPercentage = 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); +} + +std::unordered_map menuTheme = { { COLOR_WHITE, UIWidgets::Colors::White }, + { COLOR_GRAY, UIWidgets::Colors::Gray }, + { COLOR_DARK_GRAY, UIWidgets::Colors::DarkGray }, + { COLOR_INDIGO, UIWidgets::Colors::Indigo }, + { COLOR_RED, UIWidgets::Colors::Red }, + { COLOR_DARK_RED, UIWidgets::Colors::DarkRed }, + { COLOR_LIGHT_GREEN, UIWidgets::Colors::LightGreen }, + { COLOR_GREEN, UIWidgets::Colors::Green }, + { COLOR_DARK_GREEN, UIWidgets::Colors::DarkGreen }, + { COLOR_YELLOW, UIWidgets::Colors::Yellow } }; + +// All the info needed for display and search of all widgets in the menu. WidgetName is the label displayed, +// `widgetCVar` is the string representation of the CVar used to store the widget value +// `widgetTooltip` is what is displayed when hovering (except when disabled, more on that later) +// `widgetType` is the WidgetType for the widget, which is what determines how the information is used in the draw +// function. all of the preceding are required parts for every widget except for the special widgets (backend dropdowns, +// separators, etc) various parts of widgetOptions are required depending on what widget type you're using +// `widgetCallback` is a lambda used for running code on widget change +// `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` pointer 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 `modifierFunc`. +// It is recommended to utilize `disabledInfo`/`DisableReason` to list out all reasons for disabling and isHidden so +// the info can be shown. +// `isHidden` just prevents the widget from being drawn under whatever circumstances you specify in the `modifierFunc` +struct widgetInfo { + std::string widgetName; // Used by all widgets + const char* widgetCVar; // Used by all widgets except + const char* widgetTooltip; + WidgetType widgetType; + WidgetOptions widgetOptions; + WidgetFunc widgetCallback = nullptr; + WidgetFunc preFunc = nullptr; + WidgetFunc postFunc = nullptr; + DisableVec activeDisables = {}; + bool isHidden = false; +}; + +// `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 taht 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 { + std::string label; + 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; + std::vector sidebarEntries; + const char* sidebarCvar; +}; +extern std::unordered_map warpPointSceneList; +extern void Warp(); + +namespace BenGui { +extern std::shared_ptr> availableWindowBackends; +extern std::unordered_map availableWindowBackendsMap; +extern Ship::WindowBackend configWindowBackend; +extern void UpdateWindowBackendObjects(); + +std::vector menuEntries; +static ImGuiTextFilter menuSearch; +std::vector settingsSidebar; +std::vector enhancementsSidebar; +std::vector devToolsSidebar; +uint8_t searchSidebarIndex = 0; +SidebarEntry searchSidebarEntry = { + "Search", + 1, + { { { "Menu Theme", "", "Searches all menus for the given text, including tooltips.", WIDGET_SEARCH } } } +}; + +static std::map disabledMap = { + { DISABLE_FOR_CAMERAS_OFF, + { [](disabledInfo& info) -> bool { + return !CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0) && + !CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0); + }, + "Both Debug Camera and Free Look are Disabled" } }, + { DISABLE_FOR_DEBUG_CAM_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0); }, + "Debug Camera is Enabled" } }, + { DISABLE_FOR_DEBUG_CAM_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0); }, + "Debug Camera is Disabled" } }, + { DISABLE_FOR_FREE_LOOK_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0); }, + "Free Look is Enabled" } }, + { DISABLE_FOR_FREE_LOOK_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0); }, + "Free Look is Disabled" } }, + { DISABLE_FOR_AUTO_SAVE_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Saving.Autosave", 0); }, + "AutoSave is Disabled" } }, + { 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_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("gMatchRefreshRate", 0); }, + "Match Refresh Rate is Enabled" } }, + { DISABLE_FOR_MOTION_BLUR_MODE, + { [](disabledInfo& info) -> bool { + info.value = CVarGetInteger("gEnhancements.Graphics.MotionBlur.Mode", 0); + return !info.value; + }, + "Motion Blur Mode mismatch" } }, + { DISABLE_FOR_MOTION_BLUR_OFF, + { [](disabledInfo& info) -> bool { return !R_MOTION_BLUR_ENABLED; }, "Motion Blur is Disabled" } }, + { DISABLE_FOR_FRAME_ADVANCE_OFF, + { [](disabledInfo& info) -> bool { return !(gPlayState != nullptr && gPlayState->frameAdvCtx.enabled); }, + "Frame Advance is Disabled" } }, + { DISABLE_FOR_WARP_POINT_NOT_SET, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(WARP_POINT_CVAR "Saved", 0); }, + "Warp Point Not Saved" } } +}; + +std::unordered_map menuThemeOptions = { + { COLOR_WHITE, "White" }, + { COLOR_GRAY, "Gray" }, + { COLOR_DARK_GRAY, "Dark Gray" }, + { COLOR_INDIGO, "Indigo" }, + { COLOR_RED, "Red" }, + { COLOR_DARK_RED, "Dark Red" }, + { COLOR_LIGHT_GREEN, "Light Green" }, + { COLOR_GREEN, "Green" }, + { COLOR_DARK_GREEN, "Dark Green" }, + { COLOR_YELLOW, "Yellow" }, +}; + +static const std::unordered_map alwaysWinDoggyraceOptions = { + { ALWAYS_WIN_DOGGY_RACE_OFF, "Off" }, + { ALWAYS_WIN_DOGGY_RACE_MASKOFTRUTH, "When owning Mask of Truth" }, + { ALWAYS_WIN_DOGGY_RACE_ALWAYS, "Always" }, +}; + +static const std::unordered_map clockTypeOptions = { + { CLOCK_TYPE_ORIGINAL, "Original" }, + { CLOCK_TYPE_3DS, "MM3D style" }, + { CLOCK_TYPE_TEXT_BASED, "Text only" }, +}; + +static const std::unordered_map textureFilteringMap = { + { FILTER_THREE_POINT, "Three-Point" }, + { FILTER_LINEAR, "Linear" }, + { FILTER_NONE, "None" }, +}; + +static const std::unordered_map motionBlurOptions = { + { MOTION_BLUR_DYNAMIC, "Dynamic (default)" }, + { MOTION_BLUR_ALWAYS_OFF, "Always Off" }, + { MOTION_BLUR_ALWAYS_ON, "Always On" }, +}; + +static const std::unordered_map debugSaveOptions = { + { DEBUG_SAVE_INFO_COMPLETE, "100\% save" }, + { DEBUG_SAVE_INFO_VANILLA_DEBUG, "Vanilla debug save" }, + { DEBUG_SAVE_INFO_NONE, "Empty save" }, +}; + +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 audioBackendsMap = { + { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, + { Ship::AudioBackend::SDL, "SDL" }, +}; + +static std::unordered_map windowBackendsMap = { + { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" }, + { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" }, + { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" }, +}; + +static const std::unordered_map timeStopOptions = { + { TIME_STOP_OFF, "Off" }, + { TIME_STOP_TEMPLES, "Temples" }, + { TIME_STOP_TEMPLES_DUNGEONS, "Temples + Mini Dungeons" }, +}; + +void FreeLookPitchMinMax() { + f32 maxY = CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f); + f32 minY = CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f); + CVarSetFloat("gEnhancements.Camera.FreeLook.MaxPitch", std::max(maxY, minY)); + CVarSetFloat("gEnhancements.Camera.FreeLook.MinPitch", std::min(maxY, minY)); +} + +void AddSettings() { + // General Settings + settingsSidebar.push_back( + { "General", + 3, + { { + { "Menu Theme", + "gSettings.MenuTheme", + "Changes the Theme of the Menu Widgets.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = menuThemeOptions } }, +#if not defined(__SWITCH__) and not defined(__WIIU__) + { "Menubar Controller Navigation", CVAR_IMGUI_CONTROLLER_NAV, + "Allows controller navigation of the SOH menu bar (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. DEV NOTE: SDL is weird currently, pad button only " + "works with menubar open.", + WIDGET_CVAR_CHECKBOX }, + { "Cursor Always Visible", + "gSettings.CursorVisibility", + "Makes the cursor always visible, even in full screen.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetForceCursorVisibility( + CVarGetInteger("gSettings.CursorVisibility", 0)); + } }, +#endif + { "Open App Files Folder", + "", + "Opens the folder that contains the save and mods folders, etc.", + WIDGET_BUTTON, + {}, + [](widgetInfo& info) { + std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); + SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); + } }, + { "Search In Sidebar", + "gSettings.SidebarSearch", + "Displays the Search menu as a sidebar entry in Settings instead of in the header.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { + if (CVarGetInteger("gSettings.SidebarSearch", 0)) { + menuEntries[0].sidebarEntries.insert(menuEntries[0].sidebarEntries.begin() + searchSidebarIndex, + searchSidebarEntry); + CVarSetInteger(menuEntries[0].sidebarCvar, CVarGetInteger(menuEntries[0].sidebarCvar, 0) + 1); + } else { + menuEntries[0].sidebarEntries.erase(menuEntries[0].sidebarEntries.begin() + searchSidebarIndex); + CVarSetInteger(menuEntries[0].sidebarCvar, CVarGetInteger(menuEntries[0].sidebarCvar, 0) - 1); + } + } }, + { "Search Input Autofocus", "gSettings.SearchAutofocus", + "Search input box gets autofocus when visible. Does not affect using other widgets.", + WIDGET_CVAR_CHECKBOX }, + } } }); + // Audio Settings + settingsSidebar.push_back( + { "Audio", + 3, + { { { "Master Volume: %.0f%%", + "gSettings.Audio.MasterVolume", + "Adjust overall sound volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true } }, + { "Main Music Volume: %.0f%%", + "gSettings.Audio.MainMusicVolume", + "Adjust the Background Music volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_BGM_MAIN, + CVarGetFloat("gSettings.Audio.MainMusicVolume", 1.0f)); + } }, + { "Sub Music Volume: %.0f%%", + "gSettings.Audio.SubMusicVolume", + "Adjust the Sub Music volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_BGM_SUB, + CVarGetFloat("gSettings.Audio.SubMusicVolume", 1.0f)); + } }, + { "Sound Effects Volume: %.0f%%", + "gSettings.Audio.SoundEffectsVolume", + "Adjust the Sound Effects volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_SFX, + CVarGetFloat("gSettings.Audio.SoundEffectsVolume", 1.0f)); + } }, + { "Fanfare Volume: %.0f%%", + "gSettings.Audio.FanfareVolume", + "Adjust the Fanfare volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_FANFARE, + CVarGetFloat("gSettings.Audio.FanfareVolume", 1.0f)); + } }, + { "Ambience Volume: %.0f%%", + "gSettings.Audio.AmbienceVolume", + "Adjust the Ambient Sound volume.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, + .max = 100.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_AMBIENCE, + CVarGetFloat("gSettings.Audio.AmbienceVolume", 1.0f)); + } }, + { "Audio API", NULL, "Sets the audio API used by the game. Requires a relaunch to take effect.", + 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."; + } + settingsSidebar.push_back( + { "Graphics", + 3, + { { { "Toggle Fullscreen", + "gSettings.Fullscreen", + "Toggles Fullscreen On/Off.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); } }, +#ifndef __APPLE__ + { "Internal Resolution: %.0f%%", + CVAR_INTERNAL_RESOLUTION, + "Multiplies your output resolution by the value inputted, as a more intensive but effective " + "form of anti-aliasing.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 50.0f, + .max = 200.0f, + .defaultVariant = 100.0f, + .showButtons = false, + .format = "", + .isPercentage = true }, + [](widgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( + CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); + } }, +#endif +#ifndef __WIIU__ + { "Anti-aliasing (MSAA): %d", + CVAR_MSAA_VALUE, + "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.", + WIDGET_CVAR_SLIDER_INT, + { 1, 8, 1 }, + [](widgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); + } }, +#endif + + { "Current FPS: %d", + "gInterpolationFPS", + tooltip, + WIDGET_CVAR_SLIDER_INT, + { 20, maxFps, 20 }, + [](widgetInfo& info) { + int32_t defaultVariant = std::get(info.widgetOptions.defaultVariant); + if (CVarGetInteger(info.widgetCVar, defaultVariant) == defaultVariant) { + info.widgetName = "Current FPS: Original (%d)"; + } else { + info.widgetName = "Current FPS: %d"; + } + }, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_MATCH_REFRESH_RATE_ON).active) + info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON); + } }, + { "Match Refresh Rate", + "", + "Matches interpolation value to the current game's window refresh rate.", + WIDGET_BUTTON, + {}, + [](widgetInfo& info) { + int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + if (hz >= 20 && hz <= 360) { + CVarSetInteger("gInterpolationFPS", hz); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; } }, + { "Match Refresh Rate", + "gMatchRefreshRate", + "Matches interpolation value to the current game's window refresh rate.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DIRECTX).active; } }, + { "Jitter fix : >= % d FPS", + "gExtraLatencyThreshold", + "When Interpolation FPS setting is at least this threshold, add one frame of input " + "lag (e.g. 16.6 ms for 60 FPS) in order to avoid jitter. This setting allows the " + "CPU to work on one frame while GPU works on the previous frame.\nThis setting " + "should be used when your computer is too slow to do CPU + GPU work in time.", + WIDGET_CVAR_SLIDER_INT, + { 0, 360, 80 }, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; } }, + { "Renderer API (Needs reload)", NULL, "Sets the renderer API used by the game.", WIDGET_VIDEO_BACKEND }, + { "Enable Vsync", + CVAR_VSYNC_ENABLED, + "Enables Vsync.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NO_VSYNC).active; } }, + { "Windowed Fullscreen", + CVAR_SDL_WINDOWED_FULLSCREEN, + "Enables Windowed Fullscreen Mode.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NO_WINDOWED_FULLSCREEN).active; } }, + { "Allow multi-windows", + CVAR_ENABLE_MULTI_VIEWPORTS, + "Allows multiple windows to be opened at once. Requires a reload to take effect.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NO_MULTI_VIEWPORT).active; } }, + { "Texture Filter (Needs reload)", + CVAR_TEXTURE_FILTER, + "Sets the applied Texture Filtering.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = textureFilteringMap } } } } }); + // Input Editor + settingsSidebar.push_back({ "Input Editor", + 1, + { { { "Popout Input Editor", + "gWindows.BenInputEditor", + "Enables the separate Input Editor window.", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "2S2H Input Editor" } } } } }); +} +int32_t motionBlurStrength; + +void AddEnhancements() { + // Camera Snap Fix + enhancementsSidebar.push_back( + { "Camera", + 3, + { { { .widgetName = "Fixes", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Fix Targetting Camera Snap", + "gEnhancements.Camera.FixTargettingCameraSnap", + "Fixes the camera snap that occurs when you are moving and press the targetting button.", + WIDGET_CVAR_CHECKBOX, + {} } }, + // Camera Enhancements + { { .widgetName = "Cameras", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Free Look", + "gEnhancements.Camera.FreeLook.Enable", + "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.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterCameraFreeLook(); }, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_DEBUG_CAM_ON).active) + info.activeDisables.push_back(DISABLE_FOR_DEBUG_CAM_ON); + } }, + { "Camera Distance: %d", + "gEnhancements.Camera.FreeLook.MaxCameraDistance", + "Maximum Camera Distance for Free Look.", + WIDGET_CVAR_SLIDER_INT, + { 100, 900, 185 }, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_FREE_LOOK_OFF).active; } }, + { "Camera Transition Speed: %d", + "gEnhancements.Camera.FreeLook.TransitionSpeed", + "Can someone help me?", + WIDGET_CVAR_SLIDER_INT, + { 1, 900, 25 }, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_FREE_LOOK_OFF).active; } }, + { "Max Camera Height Angle: %.0f\xC2\xB0", + "gEnhancements.Camera.FreeLook.MaxPitch", + "Maximum Height of the Camera.", + WIDGET_CVAR_SLIDER_FLOAT, + { -8900.0f, 8900.0f, 7200.0f }, + [](widgetInfo& info) { FreeLookPitchMinMax(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_FREE_LOOK_OFF).active; } }, + { "Min Camera Height Angle: %.0f\xC2\xB0", + "gEnhancements.Camera.FreeLook.MinPitch", + "Minimum Height of the Camera.", + WIDGET_CVAR_SLIDER_FLOAT, + { -8900.0f, 8900.0f, -4900.0f }, + [](widgetInfo& info) { FreeLookPitchMinMax(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_FREE_LOOK_OFF).active; } }, + { "Debug Camera", + "gEnhancements.Camera.DebugCam.Enable", + "Enables free camera control.", + WIDGET_CVAR_CHECKBOX, + {}, + ([](widgetInfo& info) { RegisterDebugCam(); }), + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_FREE_LOOK_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_FREE_LOOK_ON); + } + } }, + { "Invert Camera X Axis", + "gEnhancements.Camera.RightStick.InvertXAxis", + "Inverts the Camera X Axis", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_CAMERAS_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_CAMERAS_OFF); + } + } }, + { "Invert Camera Y Axis", + "gEnhancements.Camera.RightStick.InvertYAxis", + "Inverts the Camera Y Axis", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_CAMERAS_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_CAMERAS_OFF); + } + } }, + { "Third-Person Horizontal Sensitivity: %.0f", + "gEnhancements.Camera.RightStick.CameraSensitivity.X", + "Adjust the Sensitivity of the x axis when in Third Person.", + WIDGET_CVAR_SLIDER_FLOAT, + { 1.0f, 500.0f, 100.0f }, + nullptr, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_CAMERAS_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_CAMERAS_OFF); + } + } }, + { "Third-Person Vertical Sensitivity: %.0f", + "gEnhancements.Camera.RightStick.CameraSensitivity.Y", + "Adjust the Sensitivity of the x axis when in Third Person.", + WIDGET_CVAR_SLIDER_FLOAT, + { 1.0f, 500.0f, 100.0f }, + nullptr, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_CAMERAS_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_CAMERAS_OFF); + } + } }, + { "Enable Roll (6\xC2\xB0 of Freedom)", + "gEnhancements.Camera.DebugCam.6DOF", + "This allows for all six degrees of movement with the camera, NOTE: Yaw will work " + "differently in this system, instead rotating around the focal point" + ", rather than a polar axis.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_CAM_OFF).active; } }, + { "Camera Speed: %.0f", + "gEnhancements.Camera.DebugCam.CameraSpeed", + "Adjusts the speed of the Camera.", + WIDGET_CVAR_SLIDER_FLOAT, + { 10.0f, 300.0f, 50.0f }, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_CAM_OFF).active; } } } } }); + // Cheats + enhancementsSidebar.push_back( + { "Cheats", + 3, + { { { "Infinite Health", "gCheats.InfiniteHealth", "Always have full Hearts.", WIDGET_CVAR_CHECKBOX, {} }, + { "Infinite Magic", "gCheats.InfiniteMagic", "Always have full Magic.", WIDGET_CVAR_CHECKBOX, {} }, + { "Infinite Rupees", "gCheats.InfiniteRupees", "Always have a full Wallet.", WIDGET_CVAR_CHECKBOX, {} }, + { "Infinite Consumables", "gCheats.InfiniteConsumables", + "Always have max Consumables, you must have collected the consumables first.", WIDGET_CVAR_CHECKBOX }, + { "Longer Deku Flower Glide", + "gCheats.LongerFlowerGlide", + "Allows Deku Link to glide longer, no longer dropping after a certain distance.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterLongerFlowerGlide(); } }, + { "No Clip", "gCheats.NoClip", "Allows Link to phase through collision.", WIDGET_CVAR_CHECKBOX }, + { "Unbreakable Razor Sword", "gCheats.UnbreakableRazorSword", + "Allows to Razor Sword to be used indefinitely without dulling its blade.", WIDGET_CVAR_CHECKBOX }, + { "Unrestricted Items", "gCheats.UnrestrictedItems", "Allows all Forms to use all Items.", + WIDGET_CVAR_CHECKBOX }, + { "Moon Jump on L", + "gCheats.MoonJumpOnL", + "Holding L makes you float into the air.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterMoonJumpOnL(); } }, + { "Stop Time in Dungeons", + "gCheats.TempleTimeStop", + "Stops time from advancing in selected areas. Requires a room change to update.\n\n" + "- Off: Vanilla behaviour.\n" + "- Temples: Stops time in Woodfall, Snowhead, Great Bay, and Stone Tower Temples.\n" + "- Temples + Mini Dungeons: In addition to the above temples, stops time in both Spider " + "Houses, Pirate's Fortress, Beneath the Well, Ancient Castle of Ikana, and Secret Shrine.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = timeStopOptions } } } } }); + // Gameplay Enhancements + enhancementsSidebar.push_back( + { "Gameplay", + 3, + { { { .widgetName = "Player", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Fast Deku Flower Launch", + "gEnhancements.Player.FastFlowerLaunch", + "Speeds up the time it takes to be able to get maximum height from launching out of a deku flower", + WIDGET_CVAR_CHECKBOX, + {}, + ([](widgetInfo& info) { RegisterFastFlowerLaunch(); }) }, + { "Instant Putaway", "gEnhancements.Player.InstantPutaway", + "Allows Link to instantly puts away held item without waiting.", WIDGET_CVAR_CHECKBOX }, + { "Climb speed", + "gEnhancements.PlayerMovement.ClimbSpeed", + "Increases the speed at which Link climbs vines and ladders.", + WIDGET_CVAR_SLIDER_INT, + { 1, 5, 1 } }, + { "Dpad Equips", "gEnhancements.Dpad.DpadEquips", "Allows you to equip items to your d-pad", + WIDGET_CVAR_CHECKBOX }, + { "Always Win Doggy Race", + "gEnhancements.Minigames.AlwaysWinDoggyRace", + "Makes the Doggy Race easier to win.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = alwaysWinDoggyraceOptions } }, + { "Fast Magic Arrow Equip Animation", "gEnhancements.Equipment.MagicArrowEquipSpeed", + "Removes the animation for equipping Magic Arrows.", WIDGET_CVAR_CHECKBOX }, + { "Instant Fin Boomerangs Recall", "gEnhancements.PlayerActions.InstantRecall", + "Pressing B will instantly recall the fin boomerang back to Zora Link after they are thrown.", + WIDGET_CVAR_CHECKBOX } }, + { { .widgetName = "Modes", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Play as Kafei", "gModes.PlayAsKafei", "Requires scene reload to take effect.", WIDGET_CVAR_CHECKBOX }, + { "Time Moves when you Move", + "gModes.TimeMovesWhenYouMove", + "Time only moves when Link is not standing still.", + WIDGET_CVAR_CHECKBOX, + {}, + ([](widgetInfo& info) { RegisterTimeMovesWhenYouMove(); }) } }, + { { .widgetName = "Saving", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Persistent Owl Saves", "gEnhancements.Saving.PersistentOwlSaves", + "Continuing a save will not remove the owl save. Playing Song of " + "Time, allowing the moon to crash or finishing the " + "game will remove the owl save and become the new last save.", + WIDGET_CVAR_CHECKBOX }, + { "Pause Menu Save", "gEnhancements.Saving.PauseSave", + "Re-introduce the pause menu save system. Pressing B in the pause menu will give you the " + "option to create a persistent Owl Save from your current location.\n\nWhen loading back " + "into the game, you will be placed either at the entrance of the dungeon you saved in, or " + "in South Clock Town.", + WIDGET_CVAR_CHECKBOX }, + { "Autosave", + "gEnhancements.Saving.Autosave", + "Automatically create a persistent Owl Save on the chosen interval.\n\nWhen loading " + "back into the game, you will be placed either at the entrance of the dungeon you " + "saved in, or in South Clock Town.", + WIDGET_CVAR_CHECKBOX, + {}, + ([](widgetInfo& info) { RegisterAutosave(); }) }, + { "Autosave Interval: %d minutes", + "gEnhancements.Saving.AutosaveInterval", + "Sets the interval between Autosaves.", + WIDGET_CVAR_SLIDER_INT, + { 1, 60, 5 }, + nullptr, + [](widgetInfo& info) { + if (disabledMap.at(DISABLE_FOR_AUTO_SAVE_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_AUTO_SAVE_OFF); + } + } }, + { .widgetName = "Time Cycle", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Do not reset Bottle content", "gEnhancements.Cycle.DoNotResetBottleContent", + "Playing the Song Of Time will not reset the bottles' content.", WIDGET_CVAR_CHECKBOX }, + { "Do not reset Consumables", "gEnhancements.Cycle.DoNotResetConsumables", + "Playing the Song Of Time will not reset the consumables.", WIDGET_CVAR_CHECKBOX }, + { "Do not reset Razor Sword", "gEnhancements.Cycle.DoNotResetRazorSword", + "Playing the Song Of Time will not reset the Sword back to Kokiri Sword.", WIDGET_CVAR_CHECKBOX }, + { "Do not reset Rupees", "gEnhancements.Cycle.DoNotResetRupees", + "Playing the Song Of Time will not reset the your rupees.", WIDGET_CVAR_CHECKBOX }, + { .widgetName = "Unstable", + .widgetType = WIDGET_SEPARATOR_TEXT, + .widgetOptions = { .color = UIWidgets::Colors::Yellow } }, + { "Disable Save Delay", "gEnhancements.Saving.DisableSaveDelay", + "Removes the arbitrary 2 second timer for saving from the original game. This is known to " + "cause issues when attempting the 0th Day Glitch", + WIDGET_CVAR_CHECKBOX } } } }); + // Graphics Enhancements + enhancementsSidebar.push_back( + { "Graphics", + 3, + { { { .widgetName = "Clock", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Clock Type", + "gEnhancements.Graphics.ClockType", + "Swaps between Graphical and Text only Clock types.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = clockTypeOptions } }, + { "24 Hours Clock", "gEnhancements.Graphics.24HoursClock", "Changes from a 12 Hour to a 24 Hour Clock", + WIDGET_CVAR_CHECKBOX }, + { .widgetName = "Motion Blur", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Motion Blur Mode", + "gEnhancements.Graphics.MotionBlur.Mode", + "Selects the Mode for Motion Blur.", + WIDGET_CVAR_COMBOBOX, + { .comboBoxOptions = motionBlurOptions, .labelPosition = UIWidgets::LabelPosition::None } }, + { "Interpolate", + "gEnhancements.Graphics.MotionBlur.Interpolate", + "Change motion blur capture to also happen on interpolated frames instead of only on game frames.\n" + "This notably reduces the overall motion blur strength but smooths out the trails.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_MOTION_BLUR_MODE).value == 1; } }, + { "On/Off", + "", + "Enables Motion Blur.", + WIDGET_CHECKBOX, + { .valuePointer = (bool*)&R_MOTION_BLUR_ENABLED }, + nullptr, + [](widgetInfo& info) { + info.widgetOptions.valuePointer = (bool*)&R_MOTION_BLUR_ENABLED; + info.isHidden = disabledMap.at(DISABLE_FOR_MOTION_BLUR_MODE).value != 0; + } }, + { "Strength", + "gEnhancements.Graphics.MotionBlur.Strength", + "Motion Blur strength.", + WIDGET_CVAR_SLIDER_INT, + { 0, 255, 180 }, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_MOTION_BLUR_MODE).value != 2; } }, + { "Strength", + "", + "Motion Blur strength.", + WIDGET_SLIDER_INT, + { 0, 255, 180, {}, &motionBlurStrength }, + [](widgetInfo& info) { R_MOTION_BLUR_ALPHA = motionBlurStrength; }, + [](widgetInfo& info) { + motionBlurStrength = R_MOTION_BLUR_ALPHA; + info.isHidden = disabledMap.at(DISABLE_FOR_MOTION_BLUR_MODE).value != 0 || + disabledMap.at(DISABLE_FOR_MOTION_BLUR_OFF).active; + } }, + { .widgetName = "Other", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "3D Item Drops", + "gEnhancements.Graphics.3DItemDrops", + "Makes item drops 3D", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { Register3DItemDrops(); } }, + { "Authentic Logo", "gEnhancements.Graphics.AuthenticLogo", + "Hide the game version and build details and display the authentic " + "model and texture on the boot logo start screen", + WIDGET_CVAR_CHECKBOX }, + { "Bow Reticle", "gEnhancements.Graphics.BowReticle", "Gives the bow a reticle when you draw an arrow.", + WIDGET_CVAR_CHECKBOX }, + { "Disable Black Bar Letterboxes", "gEnhancements.Graphics.DisableBlackBars", + "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.", + WIDGET_CVAR_CHECKBOX }, + { .widgetName = "Unstable", + .widgetType = WIDGET_SEPARATOR_TEXT, + .widgetOptions = { .color = UIWidgets::Colors::Yellow } }, + { "Disable Scene Geometry Distance Check", "gEnhancements.Graphics.DisableSceneGeometryDistanceCheck", + "Disables the distance check for scene geometry, allowing it to be drawn no matter how far " + "away it is from the player. This may have unintended side effects.", + WIDGET_CVAR_CHECKBOX }, + { "Widescreen Actor Culling", "gEnhancements.Graphics.ActorCullingAccountsForWidescreen", + "Adjusts the culling planes to account for widescreen resolutions. " + "This may have unintended side effects.", + WIDGET_CVAR_CHECKBOX }, + { "Increase Actor Draw Distance: %dx", + "gEnhancements.Graphics.IncreaseActorDrawDistance", + "Increase the range in which Actors are drawn. This may have unintended side effects.", + WIDGET_CVAR_SLIDER_INT, + { 1, 5, 1 }, + [](widgetInfo& info) { + CVarSetInteger("gEnhancements.Graphics.IncreaseActorUpdateDistance", + MIN(CVarGetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", 1), + CVarGetInteger("gEnhancements.Graphics.IncreaseActorUpdateDistance", 1))); + } }, + { "Increase Actor Update Distance: %dx", + "gEnhancements.Graphics.IncreaseActorUpdateDistance", + "Increase the range in which Actors are updated. This may have unintended side effects.", + WIDGET_CVAR_SLIDER_INT, + { 1, 5, 1 }, + [](widgetInfo& info) { + CVarSetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", + MAX(CVarGetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", 1), + CVarGetInteger("gEnhancements.Graphics.IncreaseActorUpdateDistance", 1))); + } } } } }); + enhancementsSidebar.push_back( + { "Items/Songs", + 3, + { // Mask Enhancements + { { .widgetName = "Masks", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Blast Mask has Powder Keg Force", "gEnhancements.Masks.BlastMaskKeg", + "Blast Mask can also destroy objects only the Powder Keg can.", WIDGET_CVAR_CHECKBOX }, + { "Fast Transformation", "gEnhancements.Masks.FastTransformation", + "Removes the delay when using transormation masks.", WIDGET_CVAR_CHECKBOX }, + { "Fierce Deity's Mask Anywhere", "gEnhancements.Masks.FierceDeitysAnywhere", + "Allow using Fierce Deity's mask outside of boss rooms.", WIDGET_CVAR_CHECKBOX }, + { "Persistent Bunny Hood", + "gEnhancements.Masks.PersistentBunnyHood.Enabled", + "Permanantly toggle a speed boost from the bunny hood by pressing " + "'A' on it in the mask menu.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { UpdatePersistentMasksState(); } }, + { "No Blast Mask Cooldown", "gEnhancements.Masks.NoBlastMaskCooldown", + "Eliminates the Cooldown between Blast Mask usage.", WIDGET_CVAR_CHECKBOX } }, + // Song Enhancements + { { .widgetName = "Ocarina", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Enable Sun's Song", "gEnhancements.Songs.EnableSunsSong", + "Enables the partially implemented Sun's Song. RIGHT-DOWN-UP-RIGHT-DOWN-UP to play it. " + "This song will make time move very fast until either Link moves to a different scene, " + "or when the time switches to a new time period.", + WIDGET_CVAR_CHECKBOX }, + { "Dpad Ocarina", "gEnhancements.Playback.DpadOcarina", "Enables using the Dpad for Ocarina playback.", + WIDGET_CVAR_CHECKBOX }, + { "Pause Owl Warp", "gEnhancements.Songs.PauseOwlWarp", + "Allows the player to use the pause menu map to owl warp instead of " + "having to play the Song of Soaring.", + WIDGET_CVAR_CHECKBOX }, + { "Zora Eggs For Bossa Nova", + "gEnhancements.Songs.ZoraEggCount", + "The number of eggs required to unlock new wave bossa nova.", + WIDGET_CVAR_SLIDER_INT, + { 1, 7, 7 } }, + { "Prevent Dropped Ocarina Inputs", "gEnhancements.Playback.NoDropOcarinaInput", + "Prevent dropping inputs when playing the ocarina quickly.", WIDGET_CVAR_CHECKBOX } } } }); + enhancementsSidebar.push_back( + { "Time Savers", + 3, + { // Cutscene Skips + { { .widgetName = "Cutscenes", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Hide Title Cards", "gEnhancements.Cutscenes.HideTitleCards", "Hides Title Cards when entering areas.", + WIDGET_CVAR_CHECKBOX }, + { "Skip Entrance Cutscenes", "gEnhancements.Cutscenes.SkipEntranceCutscenes", + "Skip cutscenes that occur when first entering a new area.", WIDGET_CVAR_CHECKBOX }, + { "Skip to File Select", "gEnhancements.Cutscenes.SkipToFileSelect", + "Skip the opening title sequence and go straight to the file select menu after boot.", + WIDGET_CVAR_CHECKBOX }, + { "Skip Intro Sequence", "gEnhancements.Cutscenes.SkipIntroSequence", + "When starting a game you will be taken straight to South Clock Town as Deku Link.", + WIDGET_CVAR_CHECKBOX }, + { "Skip Story Cutscenes", "gEnhancements.Cutscenes.SkipStoryCutscenes", + "Disclaimer: This doesn't do much yet, we will be progressively adding more skips over time.", + WIDGET_CVAR_CHECKBOX }, + { "Skip Misc Interactions", "gEnhancements.Cutscenes.SkipMiscInteractions", + "Disclaimer: This doesn't do much yet, we will be progressively adding more skips over time.", + WIDGET_CVAR_CHECKBOX } }, + // Dialogue Enhancements + { { .widgetName = "Dialogue", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Fast Bank Selection", "gEnhancements.Dialogue.FastBankSelection", + "Pressing the Z or R buttons while the Deposit/Withdrawl Rupees dialogue is open will set " + "the Rupees to Links current Rupees or 0 respectively.", + WIDGET_CVAR_CHECKBOX }, + { "Fast Text", "gEnhancements.Dialogue.FastText", + "Speeds up text rendering, and enables holding of B progress to next message.", + WIDGET_CVAR_CHECKBOX } } } }); + enhancementsSidebar.push_back( + { "Fixes", + 3, + { // Fixes + { { .widgetName = "Fixes", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Fix Ammo Count Color", "gFixes.FixAmmoCountEnvColor", + "Fixes a missing gDPSetEnvColor, which causes the ammo count to be " + "the wrong color prior to obtaining magic or other conditions.", + WIDGET_CVAR_CHECKBOX }, + { "Fix Fierce Deity Z-Target movement", "gEnhancements.Fixes.FierceDeityZTargetMovement", + "Fixes Fierce Deity movement being choppy when Z-targeting", WIDGET_CVAR_CHECKBOX }, + { "Fix Hess and Weirdshot Crash", "gEnhancements.Fixes.HessCrash", + "Fixes a crash that can occur when performing a HESS or Weirdshot.", WIDGET_CVAR_CHECKBOX }, + { "Fix Text Control Characters", "gEnhancements.Fixes.ControlCharacters", + "Fixes certain control characters not functioning properly " + "depending on their position within the text.", + WIDGET_CVAR_CHECKBOX } } } }); + enhancementsSidebar.push_back( + { "Restorations", + 3, + { // Restorations + { { .widgetName = "Restorations", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Constant Distance Backflips and Sidehops", "gEnhancements.Restorations.ConstantFlipsHops", + "Backflips and Sidehops travel a constant distance as they did in OoT.", WIDGET_CVAR_CHECKBOX }, + { "Power Crouch Stab", "gEnhancements.Restorations.PowerCrouchStab", + "Crouch stabs will use the power of Link's previous melee attack, as is in MM JP 1.0 and OoT.", + WIDGET_CVAR_CHECKBOX }, + { "Side Rolls", "gEnhancements.Restorations.SideRoll", "Restores side rolling from OoT.", + WIDGET_CVAR_CHECKBOX }, + { "Tatl ISG", "gEnhancements.Restorations.TatlISG", "Restores Navi ISG from OoT, but now with Tatl.", + WIDGET_CVAR_CHECKBOX }, + { "Woodfall Mountain Appearance", + "gEnhancements.Restorations.WoodfallMountainAppearance", + "Restores the appearance of Woodfall mountain to not look poisoned " + "when viewed from Termina Field after clearing Woodfall Temple\n\n" + "Requires a scene reload to take effect", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterWoodfallMountainAppearance(); } } } } }); + enhancementsSidebar.push_back({ "HUD Editor", + 1, + { // HUD Editor + { { "Popout HUD Editor", + "gWindows.HudEditor", + "Enables the HUD Editor window, allowing you to modify your HUD", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "HUD Editor" } } } } }); +} + +void AddDevTools() { + devToolsSidebar.push_back( + { "General", + 3, + { { { "Popout Menu", "gSettings.Menu.Popout", "Changes the menu display from overlay to windowed.", + WIDGET_CVAR_CHECKBOX }, + { "Set Warp Point", + "", + "Creates warp point that you can teleport to later", + WIDGET_BUTTON, + {}, + [](widgetInfo& info) { + Player* player = GET_PLAYER(gPlayState); + + CVarSetInteger(WARP_POINT_CVAR "Entrance", gSaveContext.save.entrance); + CVarSetInteger(WARP_POINT_CVAR "Room", gPlayState->roomCtx.curRoom.num); + CVarSetFloat(WARP_POINT_CVAR "X", player->actor.world.pos.x); + CVarSetFloat(WARP_POINT_CVAR "Y", player->actor.world.pos.y); + CVarSetFloat(WARP_POINT_CVAR "Z", player->actor.world.pos.z); + CVarSetFloat(WARP_POINT_CVAR "Rotation", player->actor.shape.rot.y); + CVarSetInteger(WARP_POINT_CVAR "Saved", 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active; } }, + { "Scene Room ID", + "", + "", + WIDGET_TEXT, + {}, + nullptr, + [](widgetInfo& info) { + u32 sceneId = Entrance_GetSceneIdAbsolute( + CVarGetInteger(WARP_POINT_CVAR "Entrance", ENTRANCE(SOUTH_CLOCK_TOWN, 0))); + info.widgetName = fmt::format("{} Room {}", warpPointSceneList[sceneId], + CVarGetInteger(WARP_POINT_CVAR "Room", 0)); + info.isHidden = disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + disabledMap.at(DISABLE_FOR_WARP_POINT_NOT_SET).active; + } }, + { ICON_FA_TIMES, + "", + "Clear warp point", + WIDGET_BUTTON, + { .size = UIWidgets::Sizes::Inline, .sameLine = true }, + [](widgetInfo& info) { + CVarClear(WARP_POINT_CVAR "Entrance"); + CVarClear(WARP_POINT_CVAR "Room"); + CVarClear(WARP_POINT_CVAR "X"); + CVarClear(WARP_POINT_CVAR "Y"); + CVarClear(WARP_POINT_CVAR "Z"); + CVarClear(WARP_POINT_CVAR "Rotation"); + CVarClear(WARP_POINT_CVAR "Saved"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + }, + [](widgetInfo& info) { + info.isHidden = disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + disabledMap.at(DISABLE_FOR_WARP_POINT_NOT_SET).active; + } }, + { "Warp", + "", + "Teleport to the set warp point", + WIDGET_BUTTON, + { .size = UIWidgets::Sizes::Inline, .sameLine = true }, + [](widgetInfo& info) { Warp(); }, + [](widgetInfo& info) { + info.isHidden = disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + disabledMap.at(DISABLE_FOR_WARP_POINT_NOT_SET).active; + } } }, + { { "Debug Mode", + "gDeveloperTools.DebugEnabled", + "Enables Debug Mode, allowing you to select maps with L + R + Z.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { + if (!CVarGetInteger("gDeveloperTools.DebugEnabled", 0)) { + CVarClear("gDeveloperTools.DebugSaveFileMode"); + CVarClear("gDeveloperTools.PreventActorUpdate"); + CVarClear("gDeveloperTools.PreventActorDraw"); + CVarClear("gDeveloperTools.PreventActorInit"); + CVarClear("gDeveloperTools.DisableObjectDependency"); + if (gPlayState != NULL) { + gPlayState->frameAdvCtx.enabled = false; + } + RegisterDebugSaveCreate(); + RegisterPreventActorUpdateHooks(); + RegisterPreventActorDrawHooks(); + RegisterPreventActorInitHooks(); + } + } }, + { "Better Map Select", + "gDeveloperTools.BetterMapSelect.Enabled", + "Overrides the original map select with a translated, more user-friendly version.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Debug Save File Mode", + "gDeveloperTools.DebugSaveFileMode", + "Change the behavior of creating saves while debug mode is enabled:\n\n" + "- Empty Save: The default 3 heart save file in first cycle\n" + "- Vanilla Debug Save: Uses the title screen save info (8 hearts, all items and masks)\n" + "- 100\% Save: All items, equipment, mask, quast status and bombers notebook complete", + WIDGET_CVAR_COMBOBOX, + { 0, 0, 0, debugSaveOptions }, + [](widgetInfo& info) { RegisterDebugSaveCreate(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Prevent Actor Update", + "gDeveloperTools.PreventActorUpdate", + "Prevents Actors from updating.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterPreventActorUpdateHooks(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Prevent Actor Draw", + "gDeveloperTools.PreventActorDraw", + "Prevents Actors from drawing.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterPreventActorDrawHooks(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Prevent Actor Init", + "gDeveloperTools.PreventActorInit", + "Prevents Actors from initializing.", + WIDGET_CVAR_CHECKBOX, + {}, + [](widgetInfo& info) { RegisterPreventActorInitHooks(); }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Disable Object Dependency", + "gDeveloperTools.DisableObjectDependency", + "Disables dependencies when loading objects.", + WIDGET_CVAR_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Log Level", + "gDeveloperTools.LogLevel", + "The log level determines which messages are printed to the " + "console. This does not affect the log file output", + WIDGET_CVAR_COMBOBOX, + { 0, 0, 0, logLevels }, + [](widgetInfo& info) { + Ship::Context::GetInstance()->GetLogger()->set_level( + (spdlog::level::level_enum)CVarGetInteger("gDeveloperTools.LogLevel", 1)); + }, + [](widgetInfo& info) { info.isHidden = disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; } }, + { "Frame Advance", + "", + "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.", + WIDGET_CHECKBOX, + {}, + nullptr, + [](widgetInfo& info) { + info.isHidden = disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + if (gPlayState != nullptr) { + info.widgetOptions.valuePointer = (bool*)&gPlayState->frameAdvCtx.enabled; + } else { + info.widgetOptions.valuePointer = (bool*)nullptr; + } + } }, + { "Advance 1", + "", + "Advance 1 frame.", + WIDGET_BUTTON, + { .size = UIWidgets::Sizes::Inline }, + [](widgetInfo& info) { CVarSetInteger("gDeveloperTools.FrameAdvanceTick", 1); }, + [](widgetInfo& info) { + info.isHidden = disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + } }, + { "Advance (Hold)", + "", + "Advance frames while the button is held.", + WIDGET_BUTTON, + { .size = UIWidgets::Sizes::Inline, .sameLine = true }, + nullptr, + [](widgetInfo& info) { + info.isHidden = disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + }, + [](widgetInfo& info) { + if (ImGui::IsItemActive()) { + CVarSetInteger("gDeveloperTools.FrameAdvanceTick", 1); + } + } } } } }); + // dev tools windows + devToolsSidebar.push_back({ "Collision Viewer", + 1, + { { { "Popout Collision Viewer", + "gWindows.CollisionViewer", + "Makes collision visible on screen", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Collision Viewer" } } } } }); + devToolsSidebar.push_back( + { "Stats", + 1, + { { { "Popout Stats", + "gOpenWindows.Stats", + "Shows the stats window, with your FPS and frametimes, and the OS you're playing on", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Stats" } } } } }); + devToolsSidebar.push_back( + { "Console", + 1, + { { { "Popout Console", + "gOpenWindows.Console", + "Enables the console window, allowing you to input commands. Type help for some examples", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Console" } } } } }); + devToolsSidebar.push_back( + { "Gfx Debugger", + 1, + { { { "Popout Gfx Debugger", + "gOpenWindows.GfxDebugger", + "Enables the Gfx Debugger window, allowing you to input commands, type help for some examples", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "GfxDebuggerWindow" } } } } }); + devToolsSidebar.push_back({ "Save Editor", + 1, + { { { "Popout Save Editor", + "gWindows.SaveEditor", + "Enables the Save Editor window, allowing you to edit your save file", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Save Editor" } } } } }); + devToolsSidebar.push_back({ "Actor Viewer", + 1, + { { { "Popout Actor Viewer", + "gWindows.ActorViewer", + "Enables the Actor Viewer window, allowing you to view actors in the world.", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Actor Viewer" } } } } }); + devToolsSidebar.push_back({ "Event Log", + 1, + { { { "Popout Event Log", + "gWindows.EventLog", + "Enables the event log window", + WIDGET_WINDOW_BUTTON, + { .size = UIWidgets::Sizes::Inline, .windowName = "Event Log" } } } } }); +} + +void SearchMenuGetItem(widgetInfo& widget) { + disabledTempTooltip = "This setting is disabled because: \n\n"; + disabledValue = false; + disabledTooltip = " "; + + if (widget.preFunc != nullptr) { + widget.activeDisables.clear(); + widget.isHidden = false; + widget.preFunc(widget); + if (widget.isHidden) { + return; + } + if (!widget.activeDisables.empty()) { + disabledValue = true; + for (auto option : widget.activeDisables) { + disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n"); + } + disabledTooltip = disabledTempTooltip.c_str(); + } + } + + if (widget.widgetOptions.sameLine) { + ImGui::SameLine(); + } + + try { + switch (widget.widgetType) { + case WIDGET_CHECKBOX: { + bool* pointer = std::get(widget.widgetOptions.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + if (UIWidgets::Checkbox( + widget.widgetName.c_str(), pointer, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .labelPosition = widget.widgetOptions.labelPosition == UIWidgets::LabelPosition::None + ? UIWidgets::LabelPosition::Near + : widget.widgetOptions.labelPosition })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + } + } break; + case WIDGET_CVAR_CHECKBOX: + if (UIWidgets::CVarCheckbox( + widget.widgetName.c_str(), widget.widgetCVar, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .defaultValue = static_cast(std::get(widget.widgetOptions.defaultVariant)), + .labelPosition = widget.widgetOptions.labelPosition == UIWidgets::LabelPosition::None + ? UIWidgets::LabelPosition::Near + : widget.widgetOptions.labelPosition })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + }; + break; + case WIDGET_AUDIO_BACKEND: { + auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetAudioBackend(); + if (UIWidgets::Combobox( + "Audio API", ¤tAudioBackend, audioBackendsMap, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = + Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1, + .disabledTooltip = "Only one audio API is available on this platform." })) { + Ship::Context::GetInstance()->GetAudio()->SetAudioBackend(currentAudioBackend); + } + } break; + case WIDGET_VIDEO_BACKEND: { + if (UIWidgets::Combobox( + "Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = availableWindowBackends->size() <= 1, + .disabledTooltip = "Only one renderer API is available on this platform." })) { + Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", + static_cast(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.widgetOptions.color != COLOR_NONE) { + ImGui::PushStyleColor(ImGuiCol_Text, widget.widgetOptions.color); + } + ImGui::SeparatorText(widget.widgetName.c_str()); + if (widget.widgetOptions.color != COLOR_NONE) { + ImGui::PopStyleColor(); + } + break; + case WIDGET_TEXT: + ImGui::AlignTextToFramePadding(); + ImGui::Text(widget.widgetName.c_str()); + break; + case WIDGET_COMBOBOX: { + int32_t* pointer = std::get(widget.widgetOptions.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + if (UIWidgets::Combobox( + widget.widgetName.c_str(), pointer, widget.widgetOptions.comboBoxOptions, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .labelPosition = widget.widgetOptions.labelPosition == UIWidgets::LabelPosition::None + ? UIWidgets::LabelPosition::Above + : widget.widgetOptions.labelPosition })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + }; + } break; + case WIDGET_CVAR_COMBOBOX: + if (UIWidgets::CVarCombobox( + widget.widgetName.c_str(), widget.widgetCVar, widget.widgetOptions.comboBoxOptions, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .defaultIndex = static_cast(std::get(widget.widgetOptions.defaultVariant)), + .labelPosition = widget.widgetOptions.labelPosition == UIWidgets::LabelPosition::None + ? UIWidgets::LabelPosition::Above + : widget.widgetOptions.labelPosition })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + } + break; + case WIDGET_SLIDER_INT: { + int32_t* pointer = std::get(widget.widgetOptions.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + if (UIWidgets::SliderInt(widget.widgetName.c_str(), pointer, + std::get(widget.widgetOptions.min), + std::get(widget.widgetOptions.max), + { + .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + }; + } break; + case WIDGET_SLIDER_FLOAT: { + float floatMin = (std::get(widget.widgetOptions.min) / 100); + float floatMax = (std::get(widget.widgetOptions.max) / 100); + float floatDefault = (std::get(widget.widgetOptions.defaultVariant) / 100); + float* pointer = std::get(widget.widgetOptions.valuePointer); + + if (pointer == nullptr) { + SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + if (UIWidgets::SliderFloat(widget.widgetName.c_str(), pointer, floatMin, floatMax, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .showButtons = widget.widgetOptions.showButtons, + .format = widget.widgetOptions.format, + .isPercentage = widget.widgetOptions.isPercentage })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + } + } break; + case WIDGET_CVAR_SLIDER_INT: + if (UIWidgets::CVarSliderInt(widget.widgetName.c_str(), widget.widgetCVar, + std::get(widget.widgetOptions.min), + std::get(widget.widgetOptions.max), + std::get(widget.widgetOptions.defaultVariant), + { + .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + }; + break; + case WIDGET_CVAR_SLIDER_FLOAT: { + float floatMin = (std::get(widget.widgetOptions.min) / 100); + float floatMax = (std::get(widget.widgetOptions.max) / 100); + float floatDefault = (std::get(widget.widgetOptions.defaultVariant) / 100); + if (UIWidgets::CVarSliderFloat(widget.widgetName.c_str(), widget.widgetCVar, floatMin, floatMax, + floatDefault, + { .color = menuTheme[menuThemeIndex], + .tooltip = widget.widgetTooltip, + .disabled = disabledValue, + .disabledTooltip = disabledTooltip, + .showButtons = widget.widgetOptions.showButtons, + .format = widget.widgetOptions.format, + .isPercentage = widget.widgetOptions.isPercentage })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + } + } break; + case WIDGET_BUTTON: + if (UIWidgets::Button(widget.widgetName.c_str(), + { menuTheme[menuThemeIndex], widget.widgetOptions.size, widget.widgetTooltip, + disabledValue, disabledTooltip })) { + if (widget.widgetCallback != nullptr) { + widget.widgetCallback(widget); + } + } + break; + case WIDGET_WINDOW_BUTTON: { + if (widget.widgetOptions.windowName == nullptr || widget.widgetOptions.windowName[0] == '\0') { + std::string msg = + fmt::format("Error drawing window contents for {}: windowName not defined", widget.widgetName); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto window = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.widgetOptions.windowName); + if (!window) { + std::string msg = fmt::format("Error drawing window contents: windowName {} does not exist", + widget.widgetOptions.windowName); + SPDLOG_ERROR(msg.c_str()); + break; + } + UIWidgets::WindowButton(widget.widgetName.c_str(), widget.widgetCVar, window, + { .size = widget.widgetOptions.size, .tooltip = widget.widgetTooltip }); + if (!window->IsVisible()) { + window->DrawElement(); + } + } break; + case WIDGET_SEARCH: { + if (ImGui::Button("Clear")) { + menuSearch.Clear(); + } + ImGui::SameLine(); + if (CVarGetInteger("gSettings.SearchAutofocus", 0) && + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + menuSearch.Draw(); + std::string menuSearchText(menuSearch.InputBuf); + + if (menuSearchText == "") { + ImGui::Text("Start typing to see results."); + return; + } + ImGui::BeginChild("Search Results"); + for (auto& [menuLabel, menuSidebar, cvar] : menuEntries) { + for (auto& sidebar : menuSidebar) { + for (auto& widgets : sidebar.columnWidgets) { + int column = 1; + for (auto& info : widgets) { + if (info.widgetType == WIDGET_SEARCH || info.widgetType == WIDGET_SEPARATOR || + info.widgetType == WIDGET_SEPARATOR_TEXT || info.isHidden) { + continue; + } + std::string widgetStr = std::string(info.widgetName) + std::string(info.widgetTooltip); + 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) { + SearchMenuGetItem(info); + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::Colors::Gray); + std::string origin = + fmt::format(" ({} -> {}, Clmn {})", menuLabel, sidebar.label, column); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + } + } + column++; + } + } + } + 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.widgetName, e.what()); + assert(false); + } +} +} // namespace BenGui diff --git a/mm/2s2h/BenGui/UIWidgets.cpp b/mm/2s2h/BenGui/UIWidgets.cpp index c50aceae7..632aac6b0 100644 --- a/mm/2s2h/BenGui/UIWidgets.cpp +++ b/mm/2s2h/BenGui/UIWidgets.cpp @@ -1,6 +1,7 @@ #include "UIWidgets.hpp" #define IMGUI_DEFINE_MATH_OPERATORS #include +#include #include #include #include @@ -389,6 +390,46 @@ bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t 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 { + *value = std::round(*value * factor) / factor; + std::stringstream ss; + ss << std::setprecision(ticks) << std::setiosflags(std::ios_base::fixed) << *value; + *value = std::stof(ss.str()); + } +} + bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options) { bool dirty = false; std::string invisibleLabelStr = "##" + std::string(label); @@ -414,8 +455,7 @@ bool SliderFloat(const char* label, float* value, float min, float max, const Fl if (options.showButtons) { if (Button("-", { .color = options.color, .size = Sizes::Inline }) && *value > min) { *value -= options.step; - if (*value < min) - *value = min; + ClampFloat(value, min, max, options.step); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); dirty = true; } @@ -427,6 +467,7 @@ bool SliderFloat(const char* label, float* value, float min, float max, const Fl if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_Float, &valueToDisplay, &minToDisplay, &maxToDisplay, options.format, options.flags)) { *value = options.isPercentage ? valueToDisplay / 100.0f : valueToDisplay; + ClampFloat(value, min, max, options.step); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); dirty = true; } @@ -435,8 +476,7 @@ bool SliderFloat(const char* label, float* value, float min, float max, const Fl ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (Button("+", { .color = options.color, .size = Sizes::Inline }) && *value < max) { *value += options.step; - if (*value > max) - *value = max; + ClampFloat(value, min, max, options.step); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); dirty = true; } diff --git a/mm/2s2h/BenGui/UIWidgets.hpp b/mm/2s2h/BenGui/UIWidgets.hpp index 506d10912..90357360d 100644 --- a/mm/2s2h/BenGui/UIWidgets.hpp +++ b/mm/2s2h/BenGui/UIWidgets.hpp @@ -12,6 +12,8 @@ namespace UIWidgets { + using SectionFunc = void(*)(); + struct TextFilters { static int FilterNumbers(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { @@ -97,6 +99,7 @@ namespace UIWidgets { void PushStyleCheckbox(const ImVec4& color = Colors::Indigo); 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 = {}); diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 57dd9fb65..26113a1d2 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -15,6 +15,7 @@ #include "z64animation.h" #include "z64bgcheck.h" #include +#include #ifdef _WIN32 #include #else @@ -95,6 +96,8 @@ CrowdControl* CrowdControl::Instance; #include "2s2h/resource/importer/BackgroundFactory.h" #include "2s2h/resource/importer/TextureAnimationFactory.h" #include "2s2h/resource/importer/KeyFrameFactory.h" +#include "window/gui/resource/Font.h" +#include "window/gui/resource/FontFactory.h" OTRGlobals* OTRGlobals::Instance; GameInteractor* GameInteractor::Instance; @@ -279,6 +282,14 @@ OTRGlobals::OTRGlobals() { } } #endif + + 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; } OTRGlobals::~OTRGlobals() { @@ -310,6 +321,37 @@ struct ExtensionEntry { std::string ext; }; +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; +} + extern "C" void OTRMessage_Init(); extern "C" void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples); extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len); @@ -1475,9 +1517,9 @@ extern "C" void OTRControllerCallback(uint8_t rumble) { Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetLED()->SetLEDColor( GetColorForControllerLED()); - static std::shared_ptr controllerConfigWindow = nullptr; + static std::shared_ptr controllerConfigWindow = nullptr; if (controllerConfigWindow == nullptr) { - controllerConfigWindow = std::dynamic_pointer_cast( + controllerConfigWindow = std::dynamic_pointer_cast( Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Input Editor")); // TODO: Add SoH Controller Config window rumble testing to upstream LUS config window // note: the current implementation may not be desired in LUS, as "true" rumble support diff --git a/mm/2s2h/BenPort.h b/mm/2s2h/BenPort.h index 81b5975b3..1bad7f7af 100644 --- a/mm/2s2h/BenPort.h +++ b/mm/2s2h/BenPort.h @@ -24,6 +24,13 @@ class OTRGlobals { public: static OTRGlobals* Instance; + ImFont* fontStandard; + ImFont* fontStandardLarger; + ImFont* fontStandardLargest; + ImFont* fontMono; + ImFont* fontMonoLarger; + ImFont* fontMonoLargest; + std::shared_ptr context; OTRGlobals(); @@ -35,6 +42,7 @@ class OTRGlobals { std::shared_ptr> ListFiles(std::string path); private: + ImFont* CreateFontWithSize(float size, std::string fontPath = ""); void CheckSaveFile(size_t sramSize) const; bool hasMasterQuest; bool hasOriginal; diff --git a/mm/2s2h/DeveloperTools/ActorViewer.cpp b/mm/2s2h/DeveloperTools/ActorViewer.cpp index 3a514785c..a3542960e 100644 --- a/mm/2s2h/DeveloperTools/ActorViewer.cpp +++ b/mm/2s2h/DeveloperTools/ActorViewer.cpp @@ -177,12 +177,6 @@ void ActorViewerWindow::UpdateElement() { } void ActorViewerWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Actor Viewer", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - return; - } - if (gPlayState != nullptr) { if (lastSceneId != gPlayState->sceneId) { ResetVariables(); @@ -442,7 +436,6 @@ void ActorViewerWindow::DrawElement() { } else { ImGui::Text("Playstate needed for actors!"); } - ImGui::End(); } void ActorViewerWindow::InitElement() { diff --git a/mm/2s2h/DeveloperTools/CollisionViewer.cpp b/mm/2s2h/DeveloperTools/CollisionViewer.cpp index 758ade313..efbc9f77a 100644 --- a/mm/2s2h/DeveloperTools/CollisionViewer.cpp +++ b/mm/2s2h/DeveloperTools/CollisionViewer.cpp @@ -42,12 +42,6 @@ static std::vector sphereVtx; // Draws the ImGui window for the collision viewer void CollisionViewerWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(390, 475), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Collision Viewer", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - return; - } - UIWidgets::CVarCheckbox("Enabled", "gCollisionViewer.Enabled"); ImGui::SameLine(); @@ -113,8 +107,6 @@ void CollisionViewerWindow::DrawElement() { { 192, 0, 192, 255 }); ImGui::EndDisabled(); - - ImGui::End(); } // Calculates the normal for a triangle at the 3 specified points diff --git a/mm/2s2h/DeveloperTools/DeveloperTools.h b/mm/2s2h/DeveloperTools/DeveloperTools.h index 93c361b99..ffcac49f5 100644 --- a/mm/2s2h/DeveloperTools/DeveloperTools.h +++ b/mm/2s2h/DeveloperTools/DeveloperTools.h @@ -1,6 +1,8 @@ #ifndef DEVELOPER_TOOLS_H #define DEVELOPER_TOOLS_H +#define WARP_POINT_CVAR "gDeveloperTools.WarpPoint." + enum DebugSaveInfo { DEBUG_SAVE_INFO_NONE, DEBUG_SAVE_INFO_VANILLA_DEBUG, diff --git a/mm/2s2h/DeveloperTools/EventLog.cpp b/mm/2s2h/DeveloperTools/EventLog.cpp index f4dd1772d..2cd60f733 100644 --- a/mm/2s2h/DeveloperTools/EventLog.cpp +++ b/mm/2s2h/DeveloperTools/EventLog.cpp @@ -259,12 +259,6 @@ void RegisterEventLogHooks() { } void EventLogWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Event Log", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - return; - } - if (UIWidgets::CVarCheckbox("Enable", "gEventLog.Enabled")) { RegisterEventLogHooks(); } @@ -384,8 +378,6 @@ void EventLogWindow::DrawElement() { ImGui::EndTable(); } - - ImGui::End(); } void EventLogWindow::InitElement() { diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp index e8aa2ddfa..8d6811628 100644 --- a/mm/2s2h/DeveloperTools/SaveEditor.cpp +++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp @@ -1949,12 +1949,6 @@ void DrawFlagsTab() { } void SaveEditorWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(480, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Save Editor", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { - ImGui::End(); - return; - } - if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("General")) { DrawGeneralTab(); @@ -1998,8 +1992,6 @@ void SaveEditorWindow::DrawElement() { ImGui::EndTabBar(); } - - ImGui::End(); } const char* textureLoad[8] = { gDungeonStrayFairyWoodfallIconTex, diff --git a/mm/2s2h/DeveloperTools/WarpPoint.cpp b/mm/2s2h/DeveloperTools/WarpPoint.cpp index 619d2ccfd..bb556eeb6 100644 --- a/mm/2s2h/DeveloperTools/WarpPoint.cpp +++ b/mm/2s2h/DeveloperTools/WarpPoint.cpp @@ -2,6 +2,7 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "window/gui/IconsFontAwesome4.h" #include "2s2h/Enhancements/GameInteractor/GameInteractor.h" +#include "2s2h/DeveloperTools/DeveloperTools.h" extern "C" { #include "z64.h" @@ -13,8 +14,6 @@ extern SaveContext gSaveContext; extern GameState* gGameState; } -#define CV "gDeveloperTools.WarpPoint." - // 2S2H Added columns to scene table: entranceSceneId, betterMapSelectIndex, humanName #define DEFINE_SCENE(_name, enumValue, _textId, _drawConfig, _restrictionFlags, _persistentCycleFlags, \ _entranceSceneId, _betterMapSelectIndex, humanName) \ @@ -29,8 +28,9 @@ std::unordered_map warpPointSceneList = { #undef DEFINE_SCENE_UNSET void Warp() { - Vec3f pos = { CVarGetFloat(CV "X", 0.0f), CVarGetFloat(CV "Y", 0.0f), CVarGetFloat(CV "Z", 0.0f) }; - s32 entrance = CVarGetInteger(CV "Entrance", ENTRANCE(SOUTH_CLOCK_TOWN, 0)); + Vec3f pos = { CVarGetFloat(WARP_POINT_CVAR "X", 0.0f), CVarGetFloat(WARP_POINT_CVAR "Y", 0.0f), + CVarGetFloat(WARP_POINT_CVAR "Z", 0.0f) }; + s32 entrance = CVarGetInteger(WARP_POINT_CVAR "Entrance", ENTRANCE(SOUTH_CLOCK_TOWN, 0)); if (gPlayState == NULL) { // If gPlayState is NULL, it means the the user opted into BootToWarpPoint and the game is starting up. This is @@ -47,7 +47,7 @@ void Warp() { gSaveContext.save.playerForm = PLAYER_FORM_HUMAN; gSaveContext.save.linkAge = 0; gSaveContext.fileNum = 0xFF; - MapSelect_LoadGame((MapSelectState*)gGameState, CVarGetInteger(CV "Entrance", 0), 0); + MapSelect_LoadGame((MapSelectState*)gGameState, CVarGetInteger(WARP_POINT_CVAR "Entrance", 0), 0); } else { // The else case, and the rest of this function is primarly relevant code copied from Play_SetRespawnData and // func_80169EFC, minus the parts that copy scene flags to scene we are warping to (this is obviously @@ -57,9 +57,9 @@ void Warp() { gPlayState->transitionType = TRANS_TYPE_INSTANT; } gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance = Entrance_Create(entrance >> 9, 0, entrance & 0xF); - gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = CVarGetInteger(CV "Room", 0); + gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = CVarGetInteger(WARP_POINT_CVAR "Room", 0); gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = pos; - gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = CVarGetFloat(CV "Rotation", 0.0f); + gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = CVarGetFloat(WARP_POINT_CVAR "Rotation", 0.0f); gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = PLAYER_PARAMS(0xFF, PLAYER_INITMODE_D); gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST; gSaveContext.respawnFlag = -8; @@ -67,8 +67,8 @@ void Warp() { void RegisterWarpPoint() { GameInteractor::Instance->RegisterGameHook([]() { - if (!CVarGetInteger("gEnhancements.Cutscenes.SkipToFileSelect", 0) && CVarGetInteger(CV "BootToWarpPoint", 0) && - CVarGetInteger(CV "Saved", 0)) { + if (!CVarGetInteger("gEnhancements.Cutscenes.SkipToFileSelect", 0) && + CVarGetInteger(WARP_POINT_CVAR "BootToWarpPoint", 0) && CVarGetInteger(WARP_POINT_CVAR "Saved", 0)) { // Normally called on console logo screen gSaveContext.seqId = (u8)NA_BGM_DISABLED; gSaveContext.ambienceId = AMBIENCE_ID_DISABLED; @@ -85,28 +85,29 @@ void RenderWarpPointSection() { if (UIWidgets::Button("Set Warp Point")) { Player* player = GET_PLAYER(gPlayState); - CVarSetInteger(CV "Entrance", gSaveContext.save.entrance); - CVarSetInteger(CV "Room", gPlayState->roomCtx.curRoom.num); - CVarSetFloat(CV "X", player->actor.world.pos.x); - CVarSetFloat(CV "Y", player->actor.world.pos.y); - CVarSetFloat(CV "Z", player->actor.world.pos.z); - CVarSetFloat(CV "Rotation", player->actor.shape.rot.y); - CVarSetInteger(CV "Saved", 1); + CVarSetInteger(WARP_POINT_CVAR "Entrance", gSaveContext.save.entrance); + CVarSetInteger(WARP_POINT_CVAR "Room", gPlayState->roomCtx.curRoom.num); + CVarSetFloat(WARP_POINT_CVAR "X", player->actor.world.pos.x); + CVarSetFloat(WARP_POINT_CVAR "Y", player->actor.world.pos.y); + CVarSetFloat(WARP_POINT_CVAR "Z", player->actor.world.pos.z); + CVarSetFloat(WARP_POINT_CVAR "Rotation", player->actor.shape.rot.y); + CVarSetInteger(WARP_POINT_CVAR "Saved", 1); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } - if (CVarGetInteger(CV "Saved", 0)) { - u32 sceneId = Entrance_GetSceneIdAbsolute(CVarGetInteger(CV "Entrance", ENTRANCE(SOUTH_CLOCK_TOWN, 0))); + if (CVarGetInteger(WARP_POINT_CVAR "Saved", 0)) { + u32 sceneId = + Entrance_GetSceneIdAbsolute(CVarGetInteger(WARP_POINT_CVAR "Entrance", ENTRANCE(SOUTH_CLOCK_TOWN, 0))); ImGui::AlignTextToFramePadding(); - ImGui::Text("%s Room %d", warpPointSceneList[sceneId], CVarGetInteger(CV "Room", 0)); + ImGui::Text("%s Room %d", warpPointSceneList[sceneId], CVarGetInteger(WARP_POINT_CVAR "Room", 0)); ImGui::SameLine(); if (UIWidgets::Button(ICON_FA_TIMES, { .size = UIWidgets::Sizes::Inline })) { - CVarClear(CV "Entrance"); - CVarClear(CV "Room"); - CVarClear(CV "X"); - CVarClear(CV "Y"); - CVarClear(CV "Z"); - CVarClear(CV "Rotation"); - CVarClear(CV "Saved"); + CVarClear(WARP_POINT_CVAR "Entrance"); + CVarClear(WARP_POINT_CVAR "Room"); + CVarClear(WARP_POINT_CVAR "X"); + CVarClear(WARP_POINT_CVAR "Y"); + CVarClear(WARP_POINT_CVAR "Z"); + CVarClear(WARP_POINT_CVAR "Rotation"); + CVarClear(WARP_POINT_CVAR "Saved"); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } ImGui::SameLine(); diff --git a/mm/assets/custom/fonts/Inconsolata-Regular.ttf b/mm/assets/custom/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d1241516bc2357fadd283371e34055d9b1cc89a8 GIT binary patch literal 101752 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 literal 0 HcmV?d00001