summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalkierian <malkierian@live.com>2024-10-02 17:25:26 -0700
committerGitHub <noreply@github.com>2024-10-02 20:25:26 -0400
commit71c2c94ece12c664fe54f9959cce40776ba36dcd (patch)
treef765d115eca4fa38dc945038bbf11d04bff1661f
parent75bc685360debc9ce4d4b8473b5a330d15909cd0 (diff)
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 <format> * 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 <Caladius@users.noreply.github.com> Co-authored-by: Garrett Cox <garrettjcox@gmail.com>
-rw-r--r--mm/2s2h/BenGui/BenGui.cpp31
-rw-r--r--mm/2s2h/BenGui/BenGui.hpp2
-rw-r--r--mm/2s2h/BenGui/BenInputEditorWindow.cpp1671
-rw-r--r--mm/2s2h/BenGui/BenInputEditorWindow.h90
-rw-r--r--mm/2s2h/BenGui/BenMenuBar.cpp6
-rw-r--r--mm/2s2h/BenGui/BenMenuBar.h1
-rw-r--r--mm/2s2h/BenGui/HudEditor.cpp12
-rw-r--r--mm/2s2h/BenGui/Menu.cpp471
-rw-r--r--mm/2s2h/BenGui/Menu.h31
-rw-r--r--mm/2s2h/BenGui/SearchableMenuItems.h1705
-rw-r--r--mm/2s2h/BenGui/UIWidgets.cpp48
-rw-r--r--mm/2s2h/BenGui/UIWidgets.hpp3
-rw-r--r--mm/2s2h/BenPort.cpp46
-rw-r--r--mm/2s2h/BenPort.h8
-rw-r--r--mm/2s2h/DeveloperTools/ActorViewer.cpp7
-rw-r--r--mm/2s2h/DeveloperTools/CollisionViewer.cpp8
-rw-r--r--mm/2s2h/DeveloperTools/DeveloperTools.h2
-rw-r--r--mm/2s2h/DeveloperTools/EventLog.cpp8
-rw-r--r--mm/2s2h/DeveloperTools/SaveEditor.cpp8
-rw-r--r--mm/2s2h/DeveloperTools/WarpPoint.cpp53
-rw-r--r--mm/assets/custom/fonts/Inconsolata-Regular.ttfbin0 -> 101752 bytes
-rw-r--r--mm/assets/custom/fonts/Montserrat-Regular.ttfbin0 -> 197624 bytes
22 files changed, 4123 insertions, 88 deletions
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<BenMenuBar> mBenMenuBar;
std::shared_ptr<Ship::GuiWindow> mConsoleWindow;
std::shared_ptr<Ship::GuiWindow> mStatsWindow;
-std::shared_ptr<Ship::GuiWindow> mInputEditorWindow;
std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
std::shared_ptr<SaveEditorWindow> mSaveEditorWindow;
@@ -35,6 +34,8 @@ std::shared_ptr<HudEditorWindow> mHudEditorWindow;
std::shared_ptr<ActorViewerWindow> mActorViewerWindow;
std::shared_ptr<CollisionViewerWindow> mCollisionViewerWindow;
std::shared_ptr<EventLogWindow> mEventLogWindow;
+std::shared_ptr<BenMenu> mBenMenu;
+std::shared_ptr<BenInputEditorWindow> mBenInputEditorWindow;
void SetupGuiElements() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
@@ -47,7 +48,7 @@ void SetupGuiElements() {
mBenMenuBar = std::make_shared<BenMenuBar>(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0));
gui->SetMenuBar(std::reinterpret_pointer_cast<Ship::GuiMenuBar>(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<BenMenu>("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<SaveEditorWindow>("gWindows.SaveEditor", "Save Editor");
+ mBenInputEditorWindow = std::make_shared<BenInputEditorWindow>("gWindows.BenInputEditor", "2S2H Input Editor");
+ gui->AddGuiWindow(mBenInputEditorWindow);
+
+ mSaveEditorWindow = std::make_shared<SaveEditorWindow>("gWindows.SaveEditor", "Save Editor", ImVec2(480, 600));
gui->AddGuiWindow(mSaveEditorWindow);
- mHudEditorWindow = std::make_shared<HudEditorWindow>("gWindows.HudEditor", "Hud Editor");
+ mHudEditorWindow = std::make_shared<HudEditorWindow>("gWindows.HudEditor", "HUD Editor", ImVec2(480, 600));
gui->AddGuiWindow(mHudEditorWindow);
- mActorViewerWindow = std::make_shared<ActorViewerWindow>("gWindows.ActorViewer", "Actor Viewer");
+ mActorViewerWindow = std::make_shared<ActorViewerWindow>("gWindows.ActorViewer", "Actor Viewer", ImVec2(520, 600));
gui->AddGuiWindow(mActorViewerWindow);
- mCollisionViewerWindow = std::make_shared<CollisionViewerWindow>("gWindows.CollisionViewer", "Collision Viewer");
+ mCollisionViewerWindow =
+ std::make_shared<CollisionViewerWindow>("gWindows.CollisionViewer", "Collision Viewer", ImVec2(390, 475));
gui->AddGuiWindow(mCollisionViewerWindow);
- mEventLogWindow = std::make_shared<EventLogWindow>("gWindows.EventLog", "Event Log");
+ mEventLogWindow = std::make_shared<EventLogWindow>("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<Ship::ShipDeviceIndex>(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<int32_t>(stick.x), static_cast<int32_t>(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<Ship::SDLAxisDirectionToButtonMapping>(mapping);
+ auto indexMapping = Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetDeviceIndexMappingFromShipDeviceIndex(mapping->GetShipDeviceIndex());
+ auto sdlIndexMapping = std::dynamic_pointer_cast<Ship::ShipDeviceIndexToSDLDeviceIndexMapping>(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<Ship::ControllerAxisDirectionMapping> 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<Ship::ControllerStick> 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(), &notchSnapAngle, 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<uint8_t, std::shared_ptr<Ship::ControllerStick>>(
+ Ship::LEFT, Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(port)->GetLeftStick()),
+ std::make_pair<uint8_t, std::shared_ptr<Ship::ControllerStick>>(
+ 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<uint8_t>(color.x * 255.0), static_cast<uint8_t>(color.y * 255.0),
+ static_cast<uint8_t>(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<CONTROLLERBUTTONS_T> bitmasks) {
+ std::set<Ship::ShipDeviceIndex> allLusDeviceIndices;
+ allLusDeviceIndices.insert(Ship::ShipDeviceIndex::Keyboard);
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappingsFromConfig()) {
+ allLusDeviceIndices.insert(lusIndex);
+ }
+
+ std::vector<std::pair<Ship::ShipDeviceIndex, bool>> 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<Ship::ShipDeviceIndex, bool>(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<Ship::ShipDeviceIndex> allLusDeviceIndices;
+ allLusDeviceIndices.insert(Ship::ShipDeviceIndex::Keyboard);
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappingsFromConfig()) {
+ allLusDeviceIndices.insert(lusIndex);
+ }
+
+ std::vector<std::pair<Ship::ShipDeviceIndex, bool>> 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<Ship::ShipDeviceIndex, bool>(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<Ship::ShipDeviceIndex> allLusDeviceIndices;
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappingsFromConfig()) {
+ allLusDeviceIndices.insert(lusIndex);
+ }
+
+ std::vector<std::pair<Ship::ShipDeviceIndex, bool>> 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<Ship::ShipDeviceIndex, bool>(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<Ship::ShipDeviceIndex> allLusDeviceIndices;
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappingsFromConfig()) {
+ allLusDeviceIndices.insert(lusIndex);
+ }
+
+ std::vector<std::pair<Ship::ShipDeviceIndex, bool>> 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<Ship::ShipDeviceIndex, bool>(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<Ship::ShipDeviceIndex, std::pair<std::string, int32_t>> indexMappings;
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappingsFromConfig()) {
+ auto sdlIndexMapping = std::static_pointer_cast<Ship::ShipDeviceIndexToSDLDeviceIndexMapping>(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<Ship::ShipDeviceIndexToSDLDeviceIndexMapping>(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<Ship::ShipDeviceIndex, std::pair<std::string, int32_t>> indexMappings;
+ for (auto [lusIndex, mapping] : Ship::Context::GetInstance()
+ ->GetControlDeck()
+ ->GetDeviceIndexMappingManager()
+ ->GetAllDeviceIndexMappings()) {
+ auto sdlIndexMapping = std::static_pointer_cast<Ship::ShipDeviceIndexToSDLDeviceIndexMapping>(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 <imgui.h>
+#include <unordered_map>
+#include <string>
+#include <vector>
+#include <set>
+#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<Ship::ControllerRumbleMapping> mRumbleMappingToTest;
+
+ // mBitmaskToMappingIds[port][bitmask] = { id0, id1, ... }
+ std::unordered_map<uint8_t, std::unordered_map<CONTROLLERBUTTONS_T, std::vector<std::string>>> mBitmaskToMappingIds;
+
+ // mStickDirectionToMappingIds[port][stick][direction] = { id0, id1, ... }
+ std::unordered_map<uint8_t,
+ std::unordered_map<uint8_t, std::unordered_map<Ship::Direction, std::vector<std::string>>>>
+ 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<CONTROLLERBUTTONS_T> mButtonsBitmasks;
+ std::set<CONTROLLERBUTTONS_T> mDpadBitmasks;
+ void DrawButtonDeviceIcons(uint8_t portIndex, std::set<CONTROLLERBUTTONS_T> 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<Ship::ShipDeviceIndex, bool> 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<Ship::GuiWindow> mInputEditorWindow;
+extern std::shared_ptr<BenInputEditorWindow> 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<ImVec2> windowTypeSizes = { {} };
+
+namespace BenGui {
+
+extern std::shared_ptr<HudEditorWindow> mHudEditorWindow;
+extern std::shared_ptr<Ship::GuiWindow> mStatsWindow;
+extern std::shared_ptr<Ship::GuiWindow> mConsoleWindow;
+extern std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
+extern std::shared_ptr<SaveEditorWindow> mSaveEditorWindow;
+extern std::shared_ptr<ActorViewerWindow> mActorViewerWindow;
+extern std::shared_ptr<CollisionViewerWindow> mCollisionViewerWindow;
+extern std::shared_ptr<EventLogWindow> mEventLogWindow;
+extern std::shared_ptr<BenInputEditorWindow> mBenInputEditorWindow;
+
+extern std::shared_ptr<std::vector<Ship::WindowBackend>> availableWindowBackends;
+extern std::unordered_map<Ship::WindowBackend, const char*> 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<ColorOption>(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<float>(windowWidth), static_cast<float>(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<float>(windowWidth), static_cast<float>(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<ImVec2> 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<SidebarEntry> 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::ConsoleWindow>(
+ 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 <libultraship/libultraship.h>
+#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 <variant>
+#include <tuple>
+
+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<DisableOption>;
+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<int32_t, const char*, float, Color_RGBA8, Color_RGB8>;
+
+// 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<int32_t, const char*> comboBoxOptions;
+ std::variant<bool*, int32_t*, float*> 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<ColorOption, ImVec4> 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<std::vector<widgetInfo>> 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<SidebarEntry> sidebarEntries;
+ const char* sidebarCvar;
+};
+extern std::unordered_map<s16, const char*> warpPointSceneList;
+extern void Warp();
+
+namespace BenGui {
+extern std::shared_ptr<std::vector<Ship::WindowBackend>> availableWindowBackends;
+extern std::unordered_map<Ship::WindowBackend, const char*> availableWindowBackendsMap;
+extern Ship::WindowBackend configWindowBackend;
+extern void UpdateWindowBackendObjects();
+
+std::vector<MainMenuEntry> menuEntries;
+static ImGuiTextFilter menuSearch;
+std::vector<SidebarEntry> settingsSidebar;
+std::vector<SidebarEntry> enhancementsSidebar;
+std::vector<SidebarEntry> 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<DisableOption, disabledInfo> 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<int32_t, const char*> 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<int32_t, const char*> 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<int32_t, const char*> clockTypeOptions = {
+ { CLOCK_TYPE_ORIGINAL, "Original" },
+ { CLOCK_TYPE_3DS, "MM3D style" },
+ { CLOCK_TYPE_TEXT_BASED, "Text only" },
+};
+
+static const std::unordered_map<int32_t, const char*> textureFilteringMap = {
+ { FILTER_THREE_POINT, "Three-Point" },
+ { FILTER_LINEAR, "Linear" },
+ { FILTER_NONE, "None" },
+};
+
+static const std::unordered_map<int32_t, const char*> motionBlurOptions = {
+ { MOTION_BLUR_DYNAMIC, "Dynamic (default)" },
+ { MOTION_BLUR_ALWAYS_OFF, "Always Off" },
+ { MOTION_BLUR_ALWAYS_ON, "Always On" },
+};
+
+static const std::unordered_map<int32_t, const char*> 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<int32_t, const char*> 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<Ship::AudioBackend, const char*> audioBackendsMap = {
+ { Ship::AudioBackend::WASAPI, "Windows Audio Session API" },
+ { Ship::AudioBackend::SDL, "SDL" },
+};
+
+static std::unordered_map<Ship::WindowBackend, const char*> windowBackendsMap = {
+ { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" },
+ { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" },
+ { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" },
+};
+
+static const std::unordered_map<int32_t, const char*> 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<int32_t>(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<bool*>(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<bool>(std::get<int32_t>(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", &currentAudioBackend, 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<int32_t>(configWindowBackend));
+ Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
+ windowBackendsMap.at(configWindowBackend));
+ Ship::Context::GetInstance()->GetConfig()->Save();
+ UpdateWindowBackendObjects();
+ }
+ } break;
+ case WIDGET_SEPARATOR:
+ ImGui::Separator();
+ break;
+ case WIDGET_SEPARATOR_TEXT:
+ if (widget.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<int32_t*>(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<uint32_t>(std::get<int32_t>(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<int32_t*>(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<int32_t>(widget.widgetOptions.min),
+ std::get<int32_t>(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<float>(widget.widgetOptions.min) / 100);
+ float floatMax = (std::get<float>(widget.widgetOptions.max) / 100);
+ float floatDefault = (std::get<float>(widget.widgetOptions.defaultVariant) / 100);
+ float* pointer = std::get<float*>(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<int32_t>(widget.widgetOptions.min),
+ std::get<int32_t>(widget.widgetOptions.max),
+ std::get<int32_t>(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<float>(widget.widgetOptions.min) / 100);
+ float floatMax = (std::get<float>(widget.widgetOptions.max) / 100);
+ float floatDefault = (std::get<float>(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 <imgui_internal.h>
+#include <sstream>
#include <libultraship/libultraship.h>
#include <string>
#include <unordered_map>
@@ -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 <libultraship/libultra/gbi.h>
+#include <Fonts.h>
#ifdef _WIN32
#include <Windows.h>
#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<Ship::ResourceInitData>();
+ initData->Format = RESOURCE_FORMAT_BINARY;
+ initData->Type = static_cast<uint32_t>(RESOURCE_TYPE_FONT);
+ initData->ResourceVersion = 0;
+ initData->Path = fontPath;
+ std::shared_ptr<Ship::Font> fontData = std::static_pointer_cast<Ship::Font>(
+ 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<Ship::InputEditorWindow> controllerConfigWindow = nullptr;
+ static std::shared_ptr<BenInputEditorWindow> controllerConfigWindow = nullptr;
if (controllerConfigWindow == nullptr) {
- controllerConfigWindow = std::dynamic_pointer_cast<Ship::InputEditorWindow>(
+ controllerConfigWindow = std::dynamic_pointer_cast<BenInputEditorWindow>(
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<Ship::Context> context;
OTRGlobals();
@@ -35,6 +42,7 @@ class OTRGlobals {
std::shared_ptr<std::vector<std::string>> 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<Vtx> 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<s16, const char*> 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<GameInteractor::OnConsoleLogoUpdate>([]() {
- 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 000000000..d1241516b
--- /dev/null
+++ b/mm/assets/custom/fonts/Inconsolata-Regular.ttf
Binary files differ
diff --git a/mm/assets/custom/fonts/Montserrat-Regular.ttf b/mm/assets/custom/fonts/Montserrat-Regular.ttf
new file mode 100644
index 000000000..f4a266dd3
--- /dev/null
+++ b/mm/assets/custom/fonts/Montserrat-Regular.ttf
Binary files differ