summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalkierian <malkierian@live.com>2025-02-04 11:43:20 -0700
committerGitHub <noreply@github.com>2025-02-04 11:43:20 -0700
commit695879c4cb6e09dae01eb6f204359619c177b79e (patch)
treef4b76c257c7450d0890357cf1659b3eb8e524c79
parent301bbd3cd9d899baf8bb16b88675f1ff3d6d7f19 (diff)
Modern Menu (#183)
* Add modern menu. Add and include LUS cvars .cmake file for macro usage in modern menu. * Un-ignore new .cmake * Copy new Resolution Editor stuff from 2ship (incomplete). Fix fullscreen checkbox. Finish changes for new UIWidgets. Cleanup ShipUtils. * Remove duplicate asm-differ entry from .gitmodules. * Re-implement Freecam options. * Cleanup freecam character follow buttons. Partially re-implement Multiplayer window as enhancement sidebar (needs text input widget to finish). Cleanup disables. --------- Co-authored-by: MegaMech <MegaMech@users.noreply.github.com>
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt1
-rw-r--r--assets/fonts/Inconsolata-Regular.ttfbin0 -> 101752 bytes
-rw-r--r--assets/fonts/Montserrat-Regular.ttfbin0 -> 197624 bytes
-rw-r--r--cmake/lus-cvars.cmake3
-rw-r--r--src/port/Engine.cpp42
-rw-r--r--src/port/Engine.h9
-rw-r--r--src/port/ShipUtils.cpp37
-rw-r--r--src/port/ShipUtils.h19
-rw-r--r--src/port/ui/FreecamWindow.cpp223
-rw-r--r--src/port/ui/FreecamWindow.h10
-rw-r--r--src/port/ui/ImguiUI.cpp692
-rw-r--r--src/port/ui/Menu.cpp801
-rw-r--r--src/port/ui/Menu.h66
-rw-r--r--src/port/ui/MenuTypes.h303
-rw-r--r--src/port/ui/MultiplayerWindow.cpp203
-rw-r--r--src/port/ui/MultiplayerWindow.h10
-rw-r--r--src/port/ui/PortMenu.cpp497
-rw-r--r--src/port/ui/PortMenu.h64
-rw-r--r--src/port/ui/ResolutionEditor.cpp836
-rw-r--r--src/port/ui/ResolutionEditor.h25
-rw-r--r--src/port/ui/UIWidgets.cpp1103
-rw-r--r--src/port/ui/UIWidgets.h912
23 files changed, 3911 insertions, 1948 deletions
diff --git a/.gitmodules b/.gitmodules
index e57c0ae29..29bd1ef14 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,9 +4,6 @@
[submodule "tools/decomp-permuter"]
path = tools/decomp-permuter
url = https://github.com/simonlindholm/decomp-permuter
-[submodule "tools/asm-differ"]
- path = tools/asm-differ
- url = https://github.com/simonlindholm/asm-differ
[submodule "doxygen-awesome-css"]
path = doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9a4bcd524..216bf854b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,7 @@ set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Spaghetti
# Add a custom module path to locate additional CMake modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
include(FindFontconfig)
+include(CMake/lus-cvars.cmake)
if (WIN32)
include(libultraship/cmake/automate-vcpkg.cmake)
diff --git a/assets/fonts/Inconsolata-Regular.ttf b/assets/fonts/Inconsolata-Regular.ttf
new file mode 100644
index 000000000..d1241516b
--- /dev/null
+++ b/assets/fonts/Inconsolata-Regular.ttf
Binary files differ
diff --git a/assets/fonts/Montserrat-Regular.ttf b/assets/fonts/Montserrat-Regular.ttf
new file mode 100644
index 000000000..f4a266dd3
--- /dev/null
+++ b/assets/fonts/Montserrat-Regular.ttf
Binary files differ
diff --git a/cmake/lus-cvars.cmake b/cmake/lus-cvars.cmake
new file mode 100644
index 000000000..90130f657
--- /dev/null
+++ b/cmake/lus-cvars.cmake
@@ -0,0 +1,3 @@
+# kept for future reference
+#set(CVAR_VSYNC_ENABLED "${CVAR_PREFIX_SETTING}.VsyncEnabled")
+include("libultraship/cmake/cvars.cmake")
diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp
index fd6db00b4..b180f3202 100644
--- a/src/port/Engine.cpp
+++ b/src/port/Engine.cpp
@@ -18,6 +18,9 @@
#include "resource/importers/UnkActorSpawnDataFactory.h"
#include "resource/importers/ArrayFactory.h"
#include <Fast3D/Fast3dWindow.h>
+#include <Fonts.h>
+#include "window/gui/resource/Font.h"
+#include "window/gui/resource/FontFactory.h"
#include <Fast3D/gfx_pc.h>
#include <Fast3D/gfx_rendering_api.h>
@@ -139,6 +142,14 @@ GameEngine::GameEngine() {
loader->RegisterResourceFactory(std::make_shared<MK64::ResourceFactoryBinaryUnkActorSpawnDataV0>(),
RESOURCE_FORMAT_BINARY, "UnkSpawnData",
static_cast<uint32_t>(MK64::ResourceType::UnkSpawnData), 0);
+
+ 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;
}
void GameEngine::Create() {
@@ -296,6 +307,37 @@ uint8_t GameEngine::GetBankIdByName(const std::string& name) {
return 0;
}
+ImFont* GameEngine::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;
+}
+
// End
extern "C" uint32_t GameEngine_GetSampleRate() {
diff --git a/src/port/Engine.h b/src/port/Engine.h
index aab8cb8d0..0b5d7943e 100644
--- a/src/port/Engine.h
+++ b/src/port/Engine.h
@@ -29,6 +29,13 @@ class GameEngine {
std::vector<std::string> sequenceTable;
std::vector<AudioSequenceData*> audioSequenceTable;
+ ImFont* fontStandard;
+ ImFont* fontStandardLarger;
+ ImFont* fontStandardLargest;
+ ImFont* fontMono;
+ ImFont* fontMonoLarger;
+ ImFont* fontMonoLargest;
+
std::unordered_map<std::string, uint8_t> bankMapTable;
GameEngine();
static void Create();
@@ -54,6 +61,8 @@ class GameEngine {
uint32_t OTRGetGameRenderHeight();
uint32_t OTRCalculateCenterOfAreaFromRightEdge(int32_t center);
uint32_t OTRCalculateCenterOfAreaFromLeftEdge(int32_t center);
+ private:
+ ImFont* CreateFontWithSize(float size, std::string fontPath = "");
};
#endif
diff --git a/src/port/ShipUtils.cpp b/src/port/ShipUtils.cpp
new file mode 100644
index 000000000..a04f4b8ce
--- /dev/null
+++ b/src/port/ShipUtils.cpp
@@ -0,0 +1,37 @@
+#include "ShipUtils.h"
+#include <libultraship/libultraship.h>
+
+extern "C" {
+#include "macros.h"
+}
+
+constexpr f32 fourByThree = 4.0f / 3.0f;
+
+extern "C" bool Ship_IsCStringEmpty(const char* str) {
+ return str == NULL || str[0] == '\0';
+}
+
+// Build vertex coordinates for a quad command
+// In order of top left, top right, bottom left, then bottom right
+// Supports flipping the texture horizontally
+extern "C" void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH) {
+ vtxList[0].v.ob[0] = xStart;
+ vtxList[0].v.ob[1] = yStart;
+ vtxList[0].v.tc[0] = (flippedH ? width : 0) << 5;
+ vtxList[0].v.tc[1] = 0 << 5;
+
+ vtxList[1].v.ob[0] = xStart + width;
+ vtxList[1].v.ob[1] = yStart;
+ vtxList[1].v.tc[0] = (flippedH ? width * 2 : width) << 5;
+ vtxList[1].v.tc[1] = 0 << 5;
+
+ vtxList[2].v.ob[0] = xStart;
+ vtxList[2].v.ob[1] = yStart + height;
+ vtxList[2].v.tc[0] = (flippedH ? width : 0) << 5;
+ vtxList[2].v.tc[1] = height << 5;
+
+ vtxList[3].v.ob[0] = xStart + width;
+ vtxList[3].v.ob[1] = yStart + height;
+ vtxList[3].v.tc[0] = (flippedH ? width * 2 : width) << 5;
+ vtxList[3].v.tc[1] = height << 5;
+}
diff --git a/src/port/ShipUtils.h b/src/port/ShipUtils.h
new file mode 100644
index 000000000..cd7bd2da9
--- /dev/null
+++ b/src/port/ShipUtils.h
@@ -0,0 +1,19 @@
+#ifndef SHIP_UTILS_H
+#define SHIP_UTILS_H
+
+#include <libultraship/libultraship.h>
+
+#ifdef __cplusplus
+
+void LoadGuiTextures();
+
+extern "C" {
+#endif
+
+bool Ship_IsCStringEmpty(const char* str);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SHIP_UTILS_H
diff --git a/src/port/ui/FreecamWindow.cpp b/src/port/ui/FreecamWindow.cpp
index 399b28690..e5cf5eea1 100644
--- a/src/port/ui/FreecamWindow.cpp
+++ b/src/port/ui/FreecamWindow.cpp
@@ -1,10 +1,13 @@
#include "FreecamWindow.h"
+#include "port/ui/PortMenu.h"
#include "UIWidgets.h"
#include "libultraship/src/Context.h"
#include <imgui.h>
+#include <map>
#include <libultraship/libultraship.h>
-#include "spdlog/spdlog.h"
+#include <spdlog/fmt/fmt.h>
+#include "spdlog/formatter.h"
#include <common_structs.h>
#include <defines.h>
#include "enhancements/freecam/freecam_engine.h"
@@ -30,80 +33,166 @@ extern u32 gFreecamControllerType;
void freecam_get_player_from_character(s32 characterId);
}
+namespace GameUI {
+extern std::shared_ptr<PortMenu> mPortMenu;
namespace Freecam {
-FreecamWindow::~FreecamWindow() {
- SPDLOG_TRACE("destruct freecam window");
-}
-
-void FreecamWindow::InitElement() {
-}
static s32 sReadyUpBool = false;
-float dampMin = 0.970;
-float dampMax = 1.0f;
-float minSpeed = 1.0;
-float maxSpeed = 30.0f;
-float minSpeedMultiplier = 1.5f;
-float maxSpeedMultiplier = 15.0f;
-float minFreecamRotateFactor = 0.0f;
-float maxFreecamRotateFactor = 1.0f;
-float minFreecamFollowFactor = 0.0f;
-float maxFreecamFollowFactor = 1.0f;
+int32_t controllerType = 0;
+std::unordered_map<int32_t, const char*> controlType = {{ 0, "Mouse/Keyboard" }, { 1, "Controller" }};
uint32_t focusPlayer;
-void FreecamWindow::DrawElement() {
- const float framerate = ImGui::GetIO().Framerate;
- const float deltatime = ImGui::GetIO().DeltaTime;
- ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
-
- ImGui::Text("Controller mode is not configured yet.");
-
- const char* items[] = { "Mouse/Keyboard", "Controller" };
- static int current_item = 0;
- if (ImGui::Combo("Dropdown", &current_item, items, IM_ARRAYSIZE(items))) {
- gFreecamControllerType = current_item;
- }
-
- ImGui::Text("Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget Player Mode: F, Next: M, Previous: N");
- ImGui::Spacing();
- UIWidgets::CVarCheckbox("Enable Flycam", "gFreecam", { .tooltip = "Allows you to fly around the course" });
-
- ImGui::Spacing();
-
- if (ImGui::SliderScalar("Camera Damping", ImGuiDataType_Float, &gDampValue, &dampMin, &dampMax, "%f")) {};
- if (ImGui::SliderScalar("Camera Speed", ImGuiDataType_Float, &gFreecamSpeed, &minSpeed, &maxSpeed, "%f")) {};
- if (ImGui::SliderScalar("Camera Speed Multiplier", ImGuiDataType_Float, &gFreecamSpeedMultiplier,
- &minSpeedMultiplier, &maxSpeedMultiplier, "%f")) {};
- if (ImGui::SliderScalar("Camera Rotation Smoothing", ImGuiDataType_Float, &gFreecamRotateSmoothingFactor,
- &minFreecamRotateFactor, &maxFreecamRotateFactor, "%f")) {};
- if (ImGui::SliderScalar("Follow Factor", ImGuiDataType_Float, &gFreecamFollowFactor,
- &minFreecamFollowFactor, &maxFreecamFollowFactor, "%f")) {};
-
- ImGui::Spacing();
-
- ImGui::Text("Target Player");
-
- if (ImGui::Button("None")) {
- fTargetPlayer = false;
- }
-
- for (size_t i = 0; i < NUM_PLAYERS; i++) {
- // These are euc-jp characters that look sort of like a hyphen -
- if (D_800E76A8[i] == NULL || D_800E76A8[i] == "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC") {
- break;
- }
- ImGui::SameLine();
- if (ImGui::Button(D_800E76A8[i])) {
- freecam_get_player_from_character(i);
- fTargetPlayer = true;
- }
- }
-
- ImGui::PopStyleColor();
+bool IsPlayerValid(const char* string) {
+ return string != NULL && (strncmp(string, "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC", 8) != 0);
}
-void FreecamWindow::UpdateElement() {
+void RegisterFreecamWidgets() {
+ mPortMenu->AddSidebarEntry("Enhancements", "Freecam", 2);
+ WidgetPath path = { "Enhancements", "Freecam", SECTION_COLUMN_1 };
+ //const float framerate = ImGui::GetIO().Framerate;
+ //const float deltatime = ImGui::GetIO().DeltaTime;
+ //ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
+
+ //mPortMenu->AddWidget(path, "Controller mode is not configured yet.", WIDGET_TEXT);
+
+ //static int current_item = 0;
+ mPortMenu->AddWidget(path, "Control Type", WIDGET_COMBOBOX)
+ .ValuePointer(&controllerType)
+ .Callback([](WidgetInfo& info) { gFreecamControllerType = (uint32_t)*std::get<int32_t*>(info.valuePointer); })
+ .Options(UIWidgets::ComboboxOptions().ComboMap(controlType));
+
+ mPortMenu->AddWidget(path, "Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget Player Mode: F, Next: M, Previous: N", WIDGET_TEXT);
+ mPortMenu->AddWidget(path, "Enable Flycam", WIDGET_CVAR_CHECKBOX)
+ .CVar("gFreecam")
+ .Options(UIWidgets::CheckboxOptions({{ .tooltip = "Allows you to fly around the course"}}));
+
+ mPortMenu->AddWidget(path, "Camera Damping", WIDGET_SLIDER_FLOAT)
+ .ValuePointer(&gDampValue)
+ .Options(UIWidgets::FloatSliderOptions().Min(0.970f).Max(1.0f).Step(0.001f).Format("%.3f"));
+ mPortMenu->AddWidget(path, "Camera Speed", WIDGET_SLIDER_FLOAT)
+ .ValuePointer(&gFreecamSpeed)
+ .Options(UIWidgets::FloatSliderOptions().Min(1.0f).Max(30.0f).Step(0.1f).Format("%.1f"));
+ mPortMenu->AddWidget(path, "Camera Speed Multiplier", WIDGET_SLIDER_FLOAT)
+ .ValuePointer(&gFreecamSpeedMultiplier)
+ .Options(UIWidgets::FloatSliderOptions().Min(1.5f).Max(15.0f).Step(0.1f).Format("%.1f"));
+ mPortMenu->AddWidget(path, "Camera Rotation Smoothing", WIDGET_SLIDER_FLOAT)
+ .ValuePointer(&gFreecamRotateSmoothingFactor)
+ .Options(UIWidgets::FloatSliderOptions().Min(0.0f).Max(1.0f).Step(0.01f).Format("%.2f"));
+ mPortMenu->AddWidget(path, "Follow Factor", WIDGET_SLIDER_FLOAT)
+ .ValuePointer(&gFreecamFollowFactor)
+ .Options(UIWidgets::FloatSliderOptions().Min(0.0f).Max(1.0f).Step(0.01f).Format("%.2f"));
+
+ mPortMenu->AddWidget(path, "Target Player", WIDGET_TEXT);
+ mPortMenu->AddWidget(path, "None", WIDGET_BUTTON)
+ .Callback([](WidgetInfo& info) { fTargetPlayer = false; });
+
+ mPortMenu->AddWidget(path, "Player 1", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[0]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[0];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(0);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 2", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[1]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[1];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(1);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 3", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[2]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[2];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(2);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 4", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[3]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[3];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(3);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 5", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[4]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[4];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(4);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 6", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[5]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[5];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(5);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 7", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[6]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[6];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(6);
+ fTargetPlayer = true;
+ });
+ mPortMenu->AddWidget(path, "Player 8", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !IsPlayerValid(D_800E76A8[7]);
+ if (!info.isHidden) {
+ info.name = D_800E76A8[7];
+ }
+ })
+ .Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
+ .SameLine(true)
+ .Callback([](WidgetInfo& info) {
+ freecam_get_player_from_character(7);
+ fTargetPlayer = true;
+ });
}
+
+static RegisterMenuInitFunc initFunc(RegisterFreecamWidgets);
+
} // namespace Freecam
+} // namespace GameUI
diff --git a/src/port/ui/FreecamWindow.h b/src/port/ui/FreecamWindow.h
index 6208666d1..4418b8d61 100644
--- a/src/port/ui/FreecamWindow.h
+++ b/src/port/ui/FreecamWindow.h
@@ -3,14 +3,4 @@
#include <libultraship/libultraship.h>
namespace Freecam {
-class FreecamWindow : public Ship::GuiWindow {
- public:
- using Ship::GuiWindow::GuiWindow;
- ~FreecamWindow();
-
- private:
- void InitElement() override;
- void DrawElement() override;
- void UpdateElement() override;
-};
} // namespace Freecam
diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp
index 9801267ce..bbaa97454 100644
--- a/src/port/ui/ImguiUI.cpp
+++ b/src/port/ui/ImguiUI.cpp
@@ -14,6 +14,7 @@
#include <libultraship/libultraship.h>
#include <Fast3D/gfx_pc.h>
#include "port/Engine.h"
+#include "PortMenu.h"
extern "C" {
extern s32 gGamestateNext;
@@ -24,26 +25,23 @@ extern "C" {
namespace GameUI {
std::shared_ptr<GameMenuBar> mGameMenuBar;
+std::shared_ptr<PortMenu> mPortMenu;
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<Ship::GuiWindow> mGameInfoWindow;
std::shared_ptr<Ship::GuiWindow> mMultiplayerWindow;
-std::shared_ptr<Ship::GuiWindow> mFreecamWindow;
-std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
void SetupGuiElements() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
- auto& style = ImGui::GetStyle();
- style.FramePadding = ImVec2(4.0f, 6.0f);
- style.ItemSpacing = ImVec2(8.0f, 6.0f);
- style.Colors[ImGuiCol_MenuBarBg] = UIWidgets::Colors::DarkGray;
-
mGameMenuBar = std::make_shared<GameMenuBar>("gOpenMenuBar", CVarGetInteger("gOpenMenuBar", 0));
gui->SetMenuBar(mGameMenuBar);
+ mPortMenu = std::make_shared<PortMenu>("gOpenMenu", "Port Menu");
+ gui->SetMenu(mPortMenu);
+
mMultiplayerWindow = gui->GetGuiWindow("Multiplayer");
if (mMultiplayerWindow == nullptr) {
SPDLOG_ERROR("Could not find multiplayer window");
@@ -75,27 +73,11 @@ void SetupGuiElements() {
SPDLOG_ERROR("Could not find input GfxDebuggerWindow");
}
- mMultiplayerWindow = std::make_shared<Multiplayer::MultiplayerWindow>("gMultiplayerWindowEnabled", "Multiplayer");
- gui->AddGuiWindow(mMultiplayerWindow);
-
- mFreecamWindow = gui->GetGuiWindow("FreecamWindow");
- if (mFreecamWindow == nullptr) {
- SPDLOG_ERROR("Could not find input FreecamWindow");
- }
-
- mFreecamWindow = std::make_shared<Freecam::FreecamWindow>("gFreecamEnabled", "Freecam");
- gui->AddGuiWindow(mFreecamWindow);
-
- mGameInfoWindow = std::make_shared<GameInfo::GameInfoWindow>("gGameInfoEnabled", "Game info");
+ mGameInfoWindow = std::make_shared<GameInfo::GameInfoWindow>("gGameInfoEnabled", "Game Info");
gui->AddGuiWindow(mGameInfoWindow);
-
- mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>(
- "gAdvancedResolutionEditorEnabled", "Advanced Resolution Settings");
- gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
}
void Destroy() {
- mAdvancedResolutionSettingsWindow = nullptr;
mGameInfoWindow = nullptr;
mConsoleWindow = nullptr;
mStatsWindow = nullptr;
@@ -125,294 +107,294 @@ static const char* filters[3] = {
};
void DrawSettingsMenu() {
- if (UIWidgets::BeginMenu("Settings")) {
- if (UIWidgets::BeginMenu("Audio")) {
- UIWidgets::CVarSliderFloat("Master Volume", "gGameMasterVolume", 0.0f, 1.0f, 1.0f, {
- .format = "%.0f%%",
- .isPercentage = true,
- });
- if (UIWidgets::CVarSliderFloat("Main Music Volume", "gMainMusicVolume", 0.0f, 1.0f, 1.0f,
- {
- .format = "%.0f%%",
- .isPercentage = true,
- })) {
- audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f));
- }
- if (UIWidgets::CVarSliderFloat("Sound Effects Volume", "gSFXMusicVolume",
- 0.0f, 1.0f, 1.0f, {
- .format = "%.0f%%",
- .isPercentage = true,
- })) {
- audio_set_player_volume(SEQ_PLAYER_SFX, CVarGetFloat("gSFXMusicVolume", 1.0f));
- }
- if (UIWidgets::CVarSliderFloat("Environment Volume", "gEnvironmentVolume",
- 0.0f, 1.0f, 1.0f, {
- .format = "%.0f%%",
- .isPercentage = true,
- })) {
- audio_set_player_volume(SEQ_PLAYER_ENV, CVarGetFloat("gEnvironmentVolume", 1.0f));
- }
-
- static std::unordered_map<Ship::AudioBackend, const char*> audioBackendNames = {
- { Ship::AudioBackend::WASAPI, "Windows Audio Session API" },
- { Ship::AudioBackend::SDL, "SDL" },
- };
-
- ImGui::Text("Audio API (Needs reload)");
- auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend();
-
- if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) {
- UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
- if (ImGui::BeginCombo("##AApi", audioBackendNames[currentAudioBackend])) {
- for (uint8_t i = 0; i <
- Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size(); i++) {
- auto backend = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->data()[i];
- if (ImGui::Selectable(audioBackendNames[backend], backend == currentAudioBackend)) {
- Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(backend);
- }
- }
- ImGui::EndCombo();
- }
- if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) {
- UIWidgets::ReEnableComponent("");
- }
-
- ImGui::EndMenu();
- }
-
- UIWidgets::Spacer(0);
-
- if (UIWidgets::BeginMenu("Controller")) {
- UIWidgets::WindowButton("Controller Mapping", "gInputEditorWindow", GameUI::mInputEditorWindow);
-
- UIWidgets::Spacer(0);
-
-#ifndef __SWITCH__
- UIWidgets::CVarCheckbox(
- "Menubar Controller Navigation", "gControlNav",
- { .tooltip = "Allows controller navigation of the SOH menu bar (Settings, Enhancements,...)\nCAUTION: "
- "This will disable game inputs while the menubar is visible.\n\nD-pad to move between "
- "items, A to select, and X to grab focus on the menu bar" });
-#endif
- UIWidgets::CVarCheckbox("Show Inputs", "gInputEnabled",
- { .tooltip = "Shows currently pressed inputs on the bottom right of the screen" });
- if (CVarGetInteger("gInputEnabled", 0)) {
- UIWidgets::CVarSliderFloat(
- "Input Scale", "gInputScale", 1.0f, 3.0f, 1.0f,
- {
- .tooltip = "Sets the on screen size of the displayed inputs from the Show Inputs setting",
- .format = "%.1fx",
- });
- }
-
- ImGui::EndMenu();
- }
-
- ImGui::EndMenu();
- }
-
- ImGui::SetCursorPosY(0.0f);
- if (UIWidgets::BeginMenu("Graphics")) {
- UIWidgets::WindowButton("Resolution Editor", "gAdvancedResolutionEditorEnabled",
- GameUI::mAdvancedResolutionSettingsWindow);
-
- UIWidgets::Spacer(0);
-
- // Previously was running every frame, and nothing was setting it? Maybe a bad copy/paste?
- // Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1));
- // UIWidgets::Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but
- // effective form of anti-aliasing");
-#ifndef __WIIU__
- if (UIWidgets::CVarSliderInt(
- "MSAA: %d", "gMSAAValue", 1, 8, 1,
- { .tooltip =
- "Activates multi-sample anti-aliasing when above 1x up to 8x for 8 samples for every pixel" })) {
- Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1));
- }
-#endif
-
- { // FPS Slider
- const int minFps = 30;
- static int maxFps;
- if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() ==
- Ship::WindowBackend::FAST3D_DXGI_DX11) {
- maxFps = 360;
- } else {
- maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
- }
- int currentFps = 0;
-#ifdef __WIIU__
- UIWidgets::Spacer(0);
- // only support divisors of 60 on the Wii U
- if (currentFps > 60) {
- currentFps = 60;
- } else {
- currentFps = 60 / (60 / currentFps);
- }
-
- int fpsSlider = 1;
- if (currentFps == 30) {
- ImGui::Text("FPS: Original (30)");
- } else {
- ImGui::Text("FPS: %d", currentFps);
- if (currentFps == 30) {
- fpsSlider = 2;
- } else { // currentFps == 60
- fpsSlider = 3;
- }
- }
- if (CVarGetInteger("gMatchRefreshRate", 0)) {
- UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
-
- if (ImGui::Button(" - ##WiiUFPS")) {
- fpsSlider--;
- }
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
-
- UIWidgets::Spacer(0);
-
- ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - 60.0f), 260.0f));
- ImGui::SliderInt("##WiiUFPSSlider", &fpsSlider, 1, 3, "", ImGuiSliderFlags_AlwaysClamp);
- ImGui::PopItemWidth();
-
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
- if (ImGui::Button(" + ##WiiUFPS")) {
- fpsSlider++;
- }
-
- if (CVarGetInteger("gMatchRefreshRate", 0)) {
- UIWidgets::ReEnableComponent("");
- }
- if (fpsSlider > 3) {
- fpsSlider = 3;
- } else if (fpsSlider < 1) {
- fpsSlider = 1;
- }
-
- if (fpsSlider == 1) {
- currentFps = 20;
- } else if (fpsSlider == 2) {
- currentFps = 30;
- } else if (fpsSlider == 3) {
- currentFps = 60;
- }
- CVarSetInteger("gInterpolationFPS", currentFps);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
-#else
- bool matchingRefreshRate =
- CVarGetInteger("gMatchRefreshRate", 0) &&
- Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != Ship::WindowBackend::FAST3D_DXGI_DX11;
- UIWidgets::CVarSliderInt((currentFps == 20) ? "FPS: Original (20)" : "FPS: %d", "gInterpolationFPS", minFps,
- maxFps, 1, { .disabled = matchingRefreshRate });
-#endif
- if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() ==
- Ship::WindowBackend::FAST3D_DXGI_DX11) {
- UIWidgets::Tooltip(
- "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. "
- "This is purely "
- "visual and does not impact game logic, execution of glitches etc.\n\n"
- "A higher target FPS than your monitor's refresh rate will waste resources, and might give a worse "
- "result.");
- } else {
- UIWidgets::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.");
- }
- } // END FPS Slider
-
- if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
- UIWidgets::Spacer(0);
- if (ImGui::Button("Match Refresh Rate")) {
- int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
- if (hz >= 30 && hz <= 360) {
- CVarSetInteger("gInterpolationFPS", hz);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- }
- }
- } else {
- UIWidgets::PaddedEnhancementCheckbox("Match Refresh Rate", "gMatchRefreshRate", true, false);
- }
-
- UIWidgets::Tooltip("Matches interpolation value to the current game's window refresh rate");
-
- if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
- UIWidgets::PaddedEnhancementSliderInt(
- CVarGetInteger("gExtraLatencyThreshold", 0) == 0 ? "Jitter fix: Off" : "Jitter fix: >= %d FPS",
- "##ExtraLatencyThreshold", "gExtraLatencyThreshold", 0, 360, "", 0, true, true, false);
- UIWidgets::Tooltip("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.");
- }
-
- UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
-
- static std::unordered_map<Ship::WindowBackend, const char*> windowBackendNames = {
- { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" },
- { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" },
- { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" },
- };
-
- ImGui::Text("Renderer API (Needs reload)");
- Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend();
- Ship::WindowBackend configWindowBackend;
- int configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1);
- if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) {
- configWindowBackend = static_cast<Ship::WindowBackend>(configWindowBackendId);
- } else {
- configWindowBackend = runningWindowBackend;
- }
-
- if (Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size() <= 1) {
- UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
- if (ImGui::BeginCombo("##RApi", windowBackendNames[configWindowBackend])) {
- for (size_t i = 0; i < Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size();
- i++) {
- auto backend = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->data()[i];
- if (ImGui::Selectable(windowBackendNames[backend], backend == configWindowBackend)) {
- Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", static_cast<int>(backend));
- Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
- windowBackendNames[backend]);
- Ship::Context::GetInstance()->GetConfig()->Save();
- }
- }
- ImGui::EndCombo();
- }
- if (Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size() <= 1) {
- UIWidgets::ReEnableComponent("");
- }
-
- if (Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) {
- UIWidgets::PaddedEnhancementCheckbox("Enable Vsync", "gVsyncEnabled", true, false);
- }
-
- if (Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen()) {
- UIWidgets::PaddedEnhancementCheckbox("Windowed fullscreen", "gSdlWindowedFullscreen", true, false);
- }
-
- if (Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports()) {
- UIWidgets::PaddedEnhancementCheckbox("Allow multi-windows", "gEnableMultiViewports", true, false, false, "",
- UIWidgets::CheckboxGraphics::Cross, true);
- UIWidgets::Tooltip("Allows windows to be able to be dragged off of the main game window. Requires a reload "
- "to take effect.");
- }
-
- // If more filters are added to LUS, make sure to add them to the filters list here
- ImGui::Text("Texture Filter (Needs reload)");
-
- UIWidgets::EnhancementCombobox("gTextureFilter", filters, 0);
-
- UIWidgets::Spacer(0);
-
- Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings();
-
- ImGui::EndMenu();
- }
+// if (UIWidgets::BeginMenu("Settings")) {
+// if (UIWidgets::BeginMenu("Audio")) {
+// UIWidgets::CVarSliderFloat("Master Volume", "gGameMasterVolume", 0.0f, 1.0f, 1.0f, {
+// .format = "%.0f%%",
+// .isPercentage = true,
+// });
+// if (UIWidgets::CVarSliderFloat("Main Music Volume", "gMainMusicVolume", 0.0f, 1.0f, 1.0f,
+// {
+// .format = "%.0f%%",
+// .isPercentage = true,
+// })) {
+// audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f));
+// }
+// if (UIWidgets::CVarSliderFloat("Sound Effects Volume", "gSFXMusicVolume",
+// 0.0f, 1.0f, 1.0f, {
+// .format = "%.0f%%",
+// .isPercentage = true,
+// })) {
+// audio_set_player_volume(SEQ_PLAYER_SFX, CVarGetFloat("gSFXMusicVolume", 1.0f));
+// }
+// if (UIWidgets::CVarSliderFloat("Environment Volume", "gEnvironmentVolume",
+// 0.0f, 1.0f, 1.0f, {
+// .format = "%.0f%%",
+// .isPercentage = true,
+// })) {
+// audio_set_player_volume(SEQ_PLAYER_ENV, CVarGetFloat("gEnvironmentVolume", 1.0f));
+// }
+//
+// static std::unordered_map<Ship::AudioBackend, const char*> audioBackendNames = {
+// { Ship::AudioBackend::WASAPI, "Windows Audio Session API" },
+// { Ship::AudioBackend::SDL, "SDL" },
+// };
+//
+// ImGui::Text("Audio API (Needs reload)");
+// auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend();
+//
+// if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) {
+// UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+// }
+// if (ImGui::BeginCombo("##AApi", audioBackendNames[currentAudioBackend])) {
+// for (uint8_t i = 0; i <
+// Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size(); i++) {
+// auto backend = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->data()[i];
+// if (ImGui::Selectable(audioBackendNames[backend], backend == currentAudioBackend)) {
+// Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(backend);
+// }
+// }
+// ImGui::EndCombo();
+// }
+// if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) {
+// UIWidgets::ReEnableComponent("");
+// }
+//
+// ImGui::EndMenu();
+// }
+//
+// UIWidgets::Spacer(0);
+//
+// if (UIWidgets::BeginMenu("Controller")) {
+// UIWidgets::WindowButton("Controller Mapping", "gInputEditorWindow", GameUI::mInputEditorWindow);
+//
+// UIWidgets::Spacer(0);
+//
+//#ifndef __SWITCH__
+// UIWidgets::CVarCheckbox(
+// "Menubar Controller Navigation", "gControlNav",
+// { .tooltip = "Allows controller navigation of the SOH menu bar (Settings, Enhancements,...)\nCAUTION: "
+// "This will disable game inputs while the menubar is visible.\n\nD-pad to move between "
+// "items, A to select, and X to grab focus on the menu bar" });
+//#endif
+// UIWidgets::CVarCheckbox("Show Inputs", "gInputEnabled",
+// { .tooltip = "Shows currently pressed inputs on the bottom right of the screen" });
+// if (CVarGetInteger("gInputEnabled", 0)) {
+// UIWidgets::CVarSliderFloat(
+// "Input Scale", "gInputScale", 1.0f, 3.0f, 1.0f,
+// {
+// .tooltip = "Sets the on screen size of the displayed inputs from the Show Inputs setting",
+// .format = "%.1fx",
+// });
+// }
+//
+// ImGui::EndMenu();
+// }
+//
+// ImGui::EndMenu();
+// }
+//
+// ImGui::SetCursorPosY(0.0f);
+// if (UIWidgets::BeginMenu("Graphics")) {
+// UIWidgets::WindowButton("Resolution Editor", "gAdvancedResolutionEditorEnabled",
+// GameUI::mAdvancedResolutionSettingsWindow);
+//
+// UIWidgets::Spacer(0);
+//
+// // Previously was running every frame, and nothing was setting it? Maybe a bad copy/paste?
+// // Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1));
+// // UIWidgets::Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but
+// // effective form of anti-aliasing");
+//#ifndef __WIIU__
+// if (UIWidgets::CVarSliderInt(
+// "MSAA: %d", "gMSAAValue", 1, 8, 1,
+// { .tooltip =
+// "Activates multi-sample anti-aliasing when above 1x up to 8x for 8 samples for every pixel" })) {
+// Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1));
+// }
+//#endif
+//
+// { // FPS Slider
+// const int minFps = 30;
+// static int maxFps;
+// if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() ==
+// Ship::WindowBackend::FAST3D_DXGI_DX11) {
+// maxFps = 360;
+// } else {
+// maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
+// }
+// int currentFps = 0;
+//#ifdef __WIIU__
+// UIWidgets::Spacer(0);
+// // only support divisors of 60 on the Wii U
+// if (currentFps > 60) {
+// currentFps = 60;
+// } else {
+// currentFps = 60 / (60 / currentFps);
+// }
+//
+// int fpsSlider = 1;
+// if (currentFps == 30) {
+// ImGui::Text("FPS: Original (30)");
+// } else {
+// ImGui::Text("FPS: %d", currentFps);
+// if (currentFps == 30) {
+// fpsSlider = 2;
+// } else { // currentFps == 60
+// fpsSlider = 3;
+// }
+// }
+// if (CVarGetInteger("gMatchRefreshRate", 0)) {
+// UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+// }
+//
+// if (ImGui::Button(" - ##WiiUFPS")) {
+// fpsSlider--;
+// }
+// ImGui::SameLine();
+// ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
+//
+// UIWidgets::Spacer(0);
+//
+// ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - 60.0f), 260.0f));
+// ImGui::SliderInt("##WiiUFPSSlider", &fpsSlider, 1, 3, "", ImGuiSliderFlags_AlwaysClamp);
+// ImGui::PopItemWidth();
+//
+// ImGui::SameLine();
+// ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
+// if (ImGui::Button(" + ##WiiUFPS")) {
+// fpsSlider++;
+// }
+//
+// if (CVarGetInteger("gMatchRefreshRate", 0)) {
+// UIWidgets::ReEnableComponent("");
+// }
+// if (fpsSlider > 3) {
+// fpsSlider = 3;
+// } else if (fpsSlider < 1) {
+// fpsSlider = 1;
+// }
+//
+// if (fpsSlider == 1) {
+// currentFps = 20;
+// } else if (fpsSlider == 2) {
+// currentFps = 30;
+// } else if (fpsSlider == 3) {
+// currentFps = 60;
+// }
+// CVarSetInteger("gInterpolationFPS", currentFps);
+// Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+//#else
+// bool matchingRefreshRate =
+// CVarGetInteger("gMatchRefreshRate", 0) &&
+// Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != Ship::WindowBackend::FAST3D_DXGI_DX11;
+// UIWidgets::CVarSliderInt((currentFps == 20) ? "FPS: Original (20)" : "FPS: %d", "gInterpolationFPS", minFps,
+// maxFps, 1, { .disabled = matchingRefreshRate });
+//#endif
+// if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() ==
+// Ship::WindowBackend::FAST3D_DXGI_DX11) {
+// UIWidgets::Tooltip(
+// "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. "
+// "This is purely "
+// "visual and does not impact game logic, execution of glitches etc.\n\n"
+// "A higher target FPS than your monitor's refresh rate will waste resources, and might give a worse "
+// "result.");
+// } else {
+// UIWidgets::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.");
+// }
+// } // END FPS Slider
+//
+// if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
+// UIWidgets::Spacer(0);
+// if (ImGui::Button("Match Refresh Rate")) {
+// int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
+// if (hz >= 30 && hz <= 360) {
+// CVarSetInteger("gInterpolationFPS", hz);
+// Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+// }
+// }
+// } else {
+// UIWidgets::PaddedEnhancementCheckbox("Match Refresh Rate", "gMatchRefreshRate", true, false);
+// }
+//
+// UIWidgets::Tooltip("Matches interpolation value to the current game's window refresh rate");
+//
+// if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
+// UIWidgets::PaddedEnhancementSliderInt(
+// CVarGetInteger("gExtraLatencyThreshold", 0) == 0 ? "Jitter fix: Off" : "Jitter fix: >= %d FPS",
+// "##ExtraLatencyThreshold", "gExtraLatencyThreshold", 0, 360, "", 0, true, true, false);
+// UIWidgets::Tooltip("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.");
+// }
+//
+// UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+//
+// static std::unordered_map<Ship::WindowBackend, const char*> windowBackendNames = {
+// { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" },
+// { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" },
+// { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" },
+// };
+//
+// ImGui::Text("Renderer API (Needs reload)");
+// Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend();
+// Ship::WindowBackend configWindowBackend;
+// int configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1);
+// if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) {
+// configWindowBackend = static_cast<Ship::WindowBackend>(configWindowBackendId);
+// } else {
+// configWindowBackend = runningWindowBackend;
+// }
+//
+// if (Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size() <= 1) {
+// UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+// }
+// if (ImGui::BeginCombo("##RApi", windowBackendNames[configWindowBackend])) {
+// for (size_t i = 0; i < Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size();
+// i++) {
+// auto backend = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->data()[i];
+// if (ImGui::Selectable(windowBackendNames[backend], backend == configWindowBackend)) {
+// Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", static_cast<int>(backend));
+// Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
+// windowBackendNames[backend]);
+// Ship::Context::GetInstance()->GetConfig()->Save();
+// }
+// }
+// ImGui::EndCombo();
+// }
+// if (Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends()->size() <= 1) {
+// UIWidgets::ReEnableComponent("");
+// }
+//
+// if (Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) {
+// UIWidgets::PaddedEnhancementCheckbox("Enable Vsync", "gVsyncEnabled", true, false);
+// }
+//
+// if (Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen()) {
+// UIWidgets::PaddedEnhancementCheckbox("Windowed fullscreen", "gSdlWindowedFullscreen", true, false);
+// }
+//
+// if (Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports()) {
+// UIWidgets::PaddedEnhancementCheckbox("Allow multi-windows", "gEnableMultiViewports", true, false, false, "",
+// UIWidgets::CheckboxGraphics::Cross, true);
+// UIWidgets::Tooltip("Allows windows to be able to be dragged off of the main game window. Requires a reload "
+// "to take effect.");
+// }
+//
+// // If more filters are added to LUS, make sure to add them to the filters list here
+// ImGui::Text("Texture Filter (Needs reload)");
+//
+// UIWidgets::EnhancementCombobox("gTextureFilter", filters, 0);
+//
+// UIWidgets::Spacer(0);
+//
+// Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings();
+//
+// ImGui::EndMenu();
+// }
}
void DrawMenuBarIcon() {
@@ -442,56 +424,15 @@ void DrawMenuBarIcon() {
}
void DrawGameMenu() {
- if (UIWidgets::BeginMenu("Spaghetti")) {
- if (UIWidgets::MenuItem("Reset",
-#ifdef __APPLE__
- "Command-R"
-#else
- "Ctrl+R"
-#endif
- )) {
- gGamestateNext = MAIN_MENU_FROM_QUIT;
- if (CVarGetInteger("gEnableDebugMode", 0) == true) {
- gMenuSelection = START_MENU;
- } else {
- gMenuSelection = LOGO_INTRO_MENU;
- }
- }
-#if !defined(__SWITCH__) && !defined(__WIIU__)
-
- if (UIWidgets::MenuItem("Toggle Fullscreen", "F11")) {
- Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen();
- }
- if (UIWidgets::MenuItem("Quit")) {
- Ship::Context::GetInstance()->GetWindow()->Close();
- }
-#endif
- ImGui::EndMenu();
- }
}
void DrawEnhancementsMenu() {
if (UIWidgets::BeginMenu("Enhancements")) {
- UIWidgets::WindowButton("Multiplayer", "gMultiplayerWindowEnabled", GameUI::mMultiplayerWindow,
- { .tooltip = "Shows the multiplayer window" });
if (UIWidgets::BeginMenu("Gameplay")) {
- UIWidgets::WindowButton("Freecam", "gFreecam", GameUI::mFreecamWindow,
- { .tooltip = "Allows you to fly around the course" });
- UIWidgets::CVarCheckbox("No multiplayer feature cuts", "gMultiplayerNoFeatureCuts",
- { .tooltip = "Allows full train and jumbotron in multiplayer, etc." });
- UIWidgets::CVarCheckbox("General Improvements", "gImprovements", { .tooltip = "General improvements to the game experience." });
- UIWidgets::CVarCheckbox(
- "No Level of Detail (LOD)", "gDisableLod",
- { .tooltip = "Disable Level of Detail (LOD) to avoid models using lower poly versions at a distance" });
-
-
- UIWidgets::CVarCheckbox("Disable Culling", "gNoCulling", { .tooltip = "Disable original culling of mk64" });
- UIWidgets::CVarSliderFloat(
- "Far Frustrum", "gFarFrustrum", 0.0f, 10000.0f, 10000.0f,
- { .tooltip = "Say how Far the Frustrum are when 'Disable Culling' are enable", .step = 10.0f });
+
ImGui::EndMenu();
}
@@ -500,16 +441,7 @@ void DrawEnhancementsMenu() {
}
void DrawCheatsMenu() {
- if (UIWidgets::BeginMenu("Cheats")) {
- UIWidgets::CVarCheckbox("Moon Jump", "gEnableMoonJump");
- UIWidgets::CVarCheckbox("Enable Custom CC", "gEnableCustomCC");
- UIWidgets::CVarSliderFloat("Custom CC", "gCustomCC", 0.0, 1000.0, 150.0, { .step = 10.0 });
- UIWidgets::CVarCheckbox("Disable Wall Collision", "gNoWallColision", { .tooltip = "Disable wall collision." });
- UIWidgets::CVarSliderFloat(
- "Min Height", "gMinHeight", -50.0f, 50.0f, 0.0f,
- { .tooltip = "When Disable Wall Collision are enable what is the minimal height you can get." });
- ImGui::EndMenu();
- }
+
}
const char* debugInfoPages[6] = {
@@ -517,31 +449,7 @@ const char* debugInfoPages[6] = {
};
void DrawDebugMenu() {
- if (UIWidgets::BeginMenu("Developer")) {
- UIWidgets::WindowButton(
- "Gfx Debugger", "gGfxDebuggerEnabled", GameUI::mGfxDebuggerWindow,
- { .tooltip =
- "Enables the Gfx Debugger window, allowing you to input commands, type help for some examples" });
-
- UIWidgets::CVarCheckbox("Debug mode", "gEnableDebugMode", { .tooltip = "Enable debug mode" });
-
- UIWidgets::CVarCheckbox("Render Collision", "gRenderCollisionMesh",
- { .tooltip = "Renders the collision mesh instead of the course mesh" });
-
- UIWidgets::Spacer(0);
- UIWidgets::WindowButton("GameInfo", "gGameInfoEnabled", GameUI::mGameInfoWindow,
- { .tooltip = "Shows the game info window, contains player and actor information" });
-
- UIWidgets::WindowButton(
- "Stats", "gStatsEnabled", GameUI::mStatsWindow,
- { .tooltip = "Shows the stats window, with your FPS and frametimes, and the OS you're playing on" });
- UIWidgets::WindowButton(
- "Console", "gConsoleEnabled", GameUI::mConsoleWindow,
- { .tooltip = "Enables the console window, allowing you to input commands, type help for some examples" });
-
- ImGui::EndMenu();
- }
}
void GameMenuBar::DrawElement() {
@@ -552,19 +460,19 @@ void GameMenuBar::DrawElement() {
ImGui::SetCursorPosY(0.0f);
- DrawSettingsMenu();
+ //DrawSettingsMenu();
ImGui::SetCursorPosY(0.0f);
- DrawEnhancementsMenu();
+ //DrawEnhancementsMenu();
ImGui::SetCursorPosY(0.0f);
- DrawCheatsMenu();
+ //DrawCheatsMenu();
ImGui::SetCursorPosY(0.0f);
- DrawDebugMenu();
+ //DrawDebugMenu();
ImGui::EndMenuBar();
}
diff --git a/src/port/ui/Menu.cpp b/src/port/ui/Menu.cpp
new file mode 100644
index 000000000..0832fb2fd
--- /dev/null
+++ b/src/port/ui/Menu.cpp
@@ -0,0 +1,801 @@
+#include "Menu.h"
+#include "UIWidgets.h"
+#include "port/Engine.h"
+#include "window/gui/GuiMenuBar.h"
+#include "window/gui/GuiElement.h"
+#include <variant>
+#include <spdlog/fmt/fmt.h>
+#include <variant>
+#include <tuple>
+
+extern "C" {
+ extern s32 gGamestateNext;
+ extern s32 gMenuSelection;
+ #include "audio/external.h"
+ #include "defines.h"
+}
+std::vector<ImVec2> windowTypeSizes = { {} };
+
+extern std::unordered_map<s16, const char*> warpPointSceneList;
+extern void Warp();
+
+namespace BenGui {}
+
+namespace Ship {
+std::string disabledTempTooltip;
+const char* disabledTooltip;
+bool disabledValue = false;
+
+bool operator==(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
+ return l.r == r.r && l.g == r.g && l.b == r.b;
+}
+
+bool operator==(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
+ return l.r == r.r && l.g == r.g && l.b == r.b && l.a == r.a;
+}
+
+bool operator<(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
+ return (l.r < r.r && l.g <= r.g && l.b <= r.b) || (l.r <= r.r && l.g < r.g && l.b <= r.b) ||
+ (l.r <= r.r && l.g <= r.g && l.b < r.b);
+}
+
+bool operator<(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
+ return (l.r < r.r && l.g <= r.g && l.b <= r.b && l.a <= r.a) ||
+ (l.r <= r.r && l.g < r.g && l.b <= r.b && l.a <= r.a) ||
+ (l.r <= r.r && l.g <= r.g && l.b < r.b && l.a <= r.a) ||
+ (l.r <= r.r && l.g <= r.g && l.b <= r.b && l.a < r.a);
+}
+
+bool operator>(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
+ return (l.r > r.r && l.g >= r.g && l.b >= r.b) || (l.r >= r.r && l.g > r.g && l.b >= r.b) ||
+ (l.r >= r.r && l.g >= r.g && l.b > r.b);
+}
+
+bool operator>(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
+ return (l.r > r.r && l.g >= r.g && l.b >= r.b && l.a >= r.a) ||
+ (l.r >= r.r && l.g > r.g && l.b >= r.b && l.a >= r.a) ||
+ (l.r >= r.r && l.g >= r.g && l.b > r.b && l.a >= r.a) ||
+ (l.r >= r.r && l.g >= r.g && l.b >= r.b && l.a > r.a);
+}
+
+uint32_t GetVectorIndexOf(std::vector<std::string>& vector, std::string value) {
+ return std::distance(vector.begin(), std::find(vector.begin(), vector.end(), value));
+}
+
+void Menu::InsertSidebarSearch() {
+ menuEntries["Settings"].sidebars.emplace("Search", searchSidebarEntry);
+ uint32_t curIndex = 0;
+ if (!Ship_IsCStringEmpty(CVarGetString(menuEntries["Settings"].sidebarCvar, ""))) {
+ curIndex = GetVectorIndexOf(menuEntries["Settings"].sidebarOrder,
+ CVarGetString(menuEntries["Settings"].sidebarCvar, ""));
+ }
+ menuEntries["Settings"].sidebarOrder.insert(menuEntries["Settings"].sidebarOrder.begin() + searchSidebarIndex,
+ "Search");
+ if (curIndex > searchSidebarIndex) {
+ CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str());
+ }
+}
+
+void Menu::RemoveSidebarSearch() {
+ uint32_t curIndex =
+ GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, CVarGetString(menuEntries["Settings"].sidebarCvar, ""));
+ menuEntries["Settings"].sidebars.erase("Search");
+ std::erase_if(menuEntries["Settings"].sidebarOrder, [](std::string& name) { return name == "Search"; });
+ if (curIndex > searchSidebarIndex) {
+ curIndex--;
+ } else if (curIndex >= menuEntries["Settings"].sidebarOrder.size()) {
+ curIndex = menuEntries["Settings"].sidebarOrder.size() - 1;
+ }
+ CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str());
+}
+
+void Menu::UpdateWindowBackendObjects() {
+ Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend();
+ int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1);
+ if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) {
+ configWindowBackend = static_cast<Ship::WindowBackend>(configWindowBackendId);
+ } else {
+ configWindowBackend = runningWindowBackend;
+ }
+
+ availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends();
+ for (auto& backend : *availableWindowBackends) {
+ availableWindowBackendsMap[backend] = windowBackendsMap.at(backend);
+ }
+}
+
+Menu::Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_,
+ UIWidgets::Colors defaultThemeIndex_)
+ : GuiWindow(cVar, name), searchSidebarIndex(searchSidebarIndex_), defaultThemeIndex(defaultThemeIndex_) {
+}
+
+void Menu::InitElement() {
+ popped = CVarGetInteger("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);
+
+ UpdateWindowBackendObjects();
+}
+
+void Menu::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;
+}
+
+uint32_t Menu::DrawSearchResults(std::string& menuSearchText) {
+ auto menuThemeIndex = static_cast<UIWidgets::Colors>(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex));
+ ImGui::BeginChild("Search Results");
+ int searchCount = 0;
+ for (auto& menuLabel : menuOrder) {
+ auto& menuEntry = menuEntries.at(menuLabel);
+ for (auto& sidebarLabel : menuEntry.sidebarOrder) {
+ auto& sidebar = menuEntry.sidebars[sidebarLabel];
+ for (int i = 0; i < sidebar.columnWidgets.size(); i++) {
+ auto& column = sidebar.columnWidgets.at(i);
+ for (auto& info : column) {
+ if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || info.type == WIDGET_SEPARATOR_TEXT ||
+ info.isHidden) {
+ continue;
+ }
+ const char* tooltip = info.options->tooltip;
+ std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : "");
+ std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower);
+ menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '),
+ menuSearchText.end());
+ std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower);
+ widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end());
+ if (widgetStr.find(menuSearchText) != std::string::npos) {
+ MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex);
+ ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray));
+ std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1);
+ ImGui::Text("%s", origin.c_str());
+ ImGui::PopStyleColor();
+ searchCount++;
+ }
+ }
+ }
+ }
+ }
+ return searchCount;
+}
+
+void Menu::AddMenuEntry(std::string entryName, const char* entryCvar) {
+ menuEntries.emplace(entryName, MainMenuEntry{ entryName, entryCvar });
+ menuOrder.push_back(entryName);
+}
+
+std::unordered_map<uint32_t, disabledInfo>& Menu::GetDisabledMap() {
+ return disabledMap;
+}
+
+void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex) {
+ disabledTempTooltip = "This setting is disabled because: \n\n";
+ disabledValue = false;
+ disabledTooltip = " ";
+
+ if (widget.preFunc != nullptr) {
+ widget.ResetDisables();
+ widget.preFunc(widget);
+ if (widget.isHidden) {
+ return;
+ }
+ if (!widget.activeDisables.empty()) {
+ widget.options->disabled = true;
+ for (auto option : widget.activeDisables) {
+ disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n");
+ }
+ widget.options->disabledTooltip = disabledTempTooltip.c_str();
+ }
+ }
+
+ if (widget.sameLine) {
+ ImGui::SameLine();
+ }
+
+ try {
+ switch (widget.type) {
+ case WIDGET_CHECKBOX: {
+ bool* pointer = std::get<bool*>(widget.valuePointer);
+ if (pointer == nullptr) {
+ SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr");
+ assert(false);
+ return;
+ }
+ auto options = std::static_pointer_cast<UIWidgets::CheckboxOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::Checkbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), pointer,
+ *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ }
+ } break;
+ case WIDGET_CVAR_CHECKBOX: {
+ auto options = std::static_pointer_cast<UIWidgets::CheckboxOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::CVarCheckbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), widget.cVar,
+ *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ };
+ } break;
+ case WIDGET_AUDIO_BACKEND: {
+ auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend();
+ UIWidgets::ComboboxOptions options = {};
+ options.color = menuThemeIndex;
+ options.tooltip = "Sets the audio API used by the game. Requires a relaunch to take effect.";
+ options.disabled = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1;
+ options.disabledTooltip = "Only one audio API is available on this platform.";
+ if (UIWidgets::Combobox("Audio API", &currentAudioBackend, audioBackendsMap, options)) {
+ Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(currentAudioBackend);
+ }
+ } break;
+ case WIDGET_VIDEO_BACKEND: {
+ UIWidgets::ComboboxOptions options = {};
+ options.color = menuThemeIndex;
+ options.tooltip = "Sets the renderer API used by the game.";
+ options.disabled = availableWindowBackends->size() <= 1;
+ options.disabledTooltip = "Only one renderer API is available on this platform.";
+ if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap,
+ options)) {
+ Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id",
+ (int32_t)(configWindowBackend));
+ Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
+ windowBackendsMap.at(configWindowBackend));
+ Ship::Context::GetInstance()->GetConfig()->Save();
+ UpdateWindowBackendObjects();
+ }
+ } break;
+ case WIDGET_SEPARATOR: {
+ ImGui::Separator();
+ } break;
+ case WIDGET_SEPARATOR_TEXT: {
+ if (widget.options->color != UIWidgets::Colors::NoColor) {
+ ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
+ }
+ ImGui::SeparatorText(widget.name.c_str());
+ if (widget.options->color != UIWidgets::Colors::NoColor) {
+ ImGui::PopStyleColor();
+ }
+ } break;
+ case WIDGET_TEXT: {
+ if (widget.options->color != UIWidgets::Colors::NoColor) {
+ ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
+ }
+ ImGui::AlignTextToFramePadding();
+ ImGui::TextWrapped("%s", widget.name.c_str());
+ if (widget.options->color != UIWidgets::Colors::NoColor) {
+ ImGui::PopStyleColor();
+ }
+ } break;
+ case WIDGET_COMBOBOX: {
+ int32_t* pointer = std::get<int32_t*>(widget.valuePointer);
+ if (pointer == nullptr) {
+ SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr");
+ assert(false);
+ return;
+ }
+ auto options = std::static_pointer_cast<UIWidgets::ComboboxOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::Combobox(widget.name.c_str(), pointer, options->comboMap, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ };
+ } break;
+ case WIDGET_CVAR_COMBOBOX: {
+ auto options = std::static_pointer_cast<UIWidgets::ComboboxOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::CVarCombobox(widget.name.c_str(), widget.cVar, options->comboMap, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ }
+ } break;
+ case WIDGET_SLIDER_INT: {
+ int32_t* pointer = std::get<int32_t*>(widget.valuePointer);
+ if (pointer == nullptr) {
+ SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr");
+ assert(false);
+ return;
+ }
+ auto options = std::static_pointer_cast<UIWidgets::IntSliderOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::SliderInt(widget.name.c_str(), pointer, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ };
+ } break;
+ case WIDGET_CVAR_SLIDER_INT: {
+ auto options = std::static_pointer_cast<UIWidgets::IntSliderOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::CVarSliderInt(widget.name.c_str(), widget.cVar, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ };
+ } break;
+ case WIDGET_SLIDER_FLOAT: {
+ float* pointer = std::get<float*>(widget.valuePointer);
+
+ if (pointer == nullptr) {
+ SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr");
+ assert(false);
+ return;
+ }
+ auto options = std::static_pointer_cast<UIWidgets::FloatSliderOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::SliderFloat(widget.name.c_str(), pointer, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ }
+ } break;
+ case WIDGET_CVAR_SLIDER_FLOAT: {
+ auto options = std::static_pointer_cast<UIWidgets::FloatSliderOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::CVarSliderFloat(widget.name.c_str(), widget.cVar, *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ }
+ } break;
+ case WIDGET_BUTTON: {
+ auto options = std::static_pointer_cast<UIWidgets::ButtonOptions>(widget.options);
+ options->color = menuThemeIndex;
+ if (UIWidgets::Button(widget.name.c_str(), *options)) {
+ if (widget.callback != nullptr) {
+ widget.callback(widget);
+ }
+ }
+ } break;
+ case WIDGET_CUSTOM: {
+ if (widget.customFunction != nullptr) {
+ widget.customFunction(widget);
+ }
+ } break;
+ case WIDGET_WINDOW_BUTTON: {
+ if (widget.windowName == nullptr || widget.windowName[0] == '\0') {
+ std::string msg =
+ fmt::format("Error drawing window contents for {}: windowName not defined", widget.name);
+ SPDLOG_ERROR(msg.c_str());
+ break;
+ }
+ auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.windowName);
+ if (!window) {
+ std::string msg =
+ fmt::format("Error drawing window contents: windowName {} does not exist", widget.windowName);
+ SPDLOG_ERROR(msg.c_str());
+ break;
+ }
+ auto options = std::static_pointer_cast<UIWidgets::ButtonOptions>(widget.options);
+ options->color = menuThemeIndex;
+ UIWidgets::WindowButton(widget.name.c_str(), widget.cVar, window, *options);
+ if (!window->IsVisible()) {
+ window->DrawElement();
+ }
+ } break;
+ case WIDGET_SEARCH: {
+ UIWidgets::PushStyleButton(menuThemeIndex);
+ if (ImGui::Button("Clear")) {
+ menuSearch.Clear();
+ }
+ ImGui::SameLine();
+ if (CVarGetInteger("gSettings.Menu.SearchAutofocus", 0) &&
+ ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
+ !ImGui::IsMouseClicked(0)) {
+ ImGui::SetKeyboardFocusHere(0);
+ }
+ UIWidgets::PushStyleCombobox(menuThemeIndex);
+ ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(menuThemeIndex));
+ menuSearch.Draw();
+ ImGui::PopStyleColor();
+ UIWidgets::PopStyleCombobox();
+ UIWidgets::PopStyleButton();
+ std::string menuSearchText(menuSearch.InputBuf);
+
+ if (menuSearchText == "") {
+ ImGui::Text("Start typing to see results.");
+ return;
+ }
+ DrawSearchResults(menuSearchText);
+ ImGui::EndChild();
+ } break;
+ default:
+ break;
+ }
+ if (widget.postFunc != nullptr) {
+ widget.postFunc(widget);
+ }
+ } catch (const std::bad_variant_access& e) {
+ SPDLOG_ERROR("Failed to draw menu item \"{}\" due to: {}", widget.name, e.what());
+ assert(false);
+ }
+}
+
+void Menu::Draw() {
+ if (!IsVisible()) {
+ return;
+ }
+ DrawElement();
+ // Sync up the IsVisible flag if it was changed by ImGui
+ SyncVisibilityConsoleVariable();
+}
+
+void Menu::DrawElement() {
+ for (auto& [reason, info] : disabledMap) {
+ info.active = info.evaluation(info);
+ }
+ auto menuThemeIndex = static_cast<UIWidgets::Colors>(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex));
+
+ 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)) {
+ 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(GameEngine::Instance->fontStandardLargest);
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImGuiStyle& style = ImGui::GetStyle();
+ windowHeight = window->WorkRect.GetHeight();
+ windowWidth = window->WorkRect.GetWidth();
+
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f));
+ const char* headerCvar = "gSettings.Menu.ActiveHeader";
+ std::string headerIndex = CVarGetString(headerCvar, "Settings");
+ ImVec2 pos = window->DC.CursorPos;
+ float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1));
+ std::vector<ImVec2> headerSizes;
+ float headerWidth = style.ItemSpacing.x;
+ bool headerSearch = !CVarGetInteger("gSettings.Menu.SidebarSearch", 0);
+ if (headerSearch) {
+ headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x;
+ }
+ for (auto& label : menuOrder) {
+ ImVec2 size = ImGui::CalcTextSize(label.c_str());
+ headerSizes.push_back(size);
+ headerWidth += size.x + style.FramePadding.x * 2;
+ if (label == headerIndex) {
+ headerWidth += style.ItemSpacing.x;
+ }
+ }
+ 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::unordered_map<std::string, 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;
+ }
+ UIWidgets::ButtonOptions options = {};
+ options.size = UIWidgets::Sizes::Inline;
+ options.tooltip = "Close Menu (Esc)";
+ if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) {
+ ToggleVisibility();
+
+ // Update gamepad navigation after close based on if other menus are still visible
+ auto mImGuiIo = &ImGui::GetIO();
+ if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) &&
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) {
+ mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
+ } else {
+ mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
+ }
+ }
+ ImGui::SameLine();
+ ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight });
+ ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight };
+ if (scrollbar) {
+ headerSelSize.y += style.ScrollbarSize;
+ }
+ bool autoFocus = CVarGetInteger("gSettings.Menu.SearchAutofocus", 0);
+ ImGui::BeginChild("Header Selection", headerSelSize,
+ ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
+ ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar);
+ uint8_t curIndex = 0;
+ for (auto& label : menuOrder) {
+ if (curIndex != 0) {
+ ImGui::SameLine();
+ }
+ auto& entry = menuEntries.at(label);
+ std::string nextIndex = label;
+ UIWidgets::PushStyleButton(menuThemeIndex);
+ if (headerIndex != label) {
+ ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
+ }
+ if (ModernMenuHeaderEntry(entry.label)) {
+ if (headerSearch) {
+ menuSearch.Clear();
+ }
+ CVarSetString(headerCvar, label.c_str());
+ CVarSave();
+ nextIndex = label;
+ }
+ if (headerIndex != label) {
+ ImGui::PopStyleColor();
+ }
+ UIWidgets::PopStyleButton();
+ if (headerIndex == label) {
+ sidebar = &entry.sidebars;
+ }
+ if (nextIndex != label) {
+ headerIndex = nextIndex;
+ }
+ curIndex++;
+ }
+ std::string menuSearchText = "";
+ if (headerSearch) {
+ ImGui::SameLine();
+ if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
+ !ImGui::IsMouseClicked(0)) {
+ ImGui::SetKeyboardFocusHere(0);
+ }
+ auto color = UIWidgets::ColorValues.at(menuThemeIndex);
+ color.w = 0.2f;
+ ImGui::PushStyleColor(ImGuiCol_FrameBg, color);
+ menuSearch.Draw("##search", 200.0f);
+ menuSearchText = menuSearch.InputBuf;
+ menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), menuSearchText.end());
+ if (menuSearchText.length() < 1) {
+ ImGui::SameLine(headerWidth - 200.0f + style.ItemSpacing.x);
+ ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search...");
+ }
+ ImGui::PopStyleColor();
+ }
+ ImGui::EndChild();
+ ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x);
+ UIWidgets::ButtonOptions options2 = {};
+ options2.color = UIWidgets::Colors::Red;
+ options2.size = UIWidgets::Sizes::Inline;
+ options2.tooltip = "Reset"
+#ifdef __APPLE__
+ " (Command-R)"
+#elif !defined(__SWITCH__) && !defined(__WIIU__)
+ " (Ctrl+R)"
+#else
+ ""
+#endif
+ ;
+ if (UIWidgets::Button(ICON_FA_UNDO, options2)) {
+ ProcessReset();
+ }
+ ImGui::SameLine();
+ UIWidgets::ButtonOptions options3 = {};
+ options3.color = UIWidgets::Colors::Red;
+ options3.size = UIWidgets::Sizes::Inline;
+ options3.tooltip = "Quit 2S2H";
+ if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) {
+ if (!popped) {
+ ToggleVisibility();
+ }
+ Ship::Context::GetInstance()->GetWindow()->Close();
+ }
+ ImGui::PopStyleVar();
+
+ pos.y += headerHeight + style.ItemSpacing.y;
+ pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1));
+ window->DrawList->AddRectFilled(pos, pos + ImVec2{ menuSize.x, 4 }, ImGui::GetColorU32({ 255, 255, 255, 255 }),
+ true, style.WindowRounding);
+ pos.y += style.ItemSpacing.y;
+ float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2;
+ float columnHeight = sectionHeight - style.ItemSpacing.y * 4;
+ ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2);
+ float sidebarWidth = 200 - style.ItemSpacing.x;
+
+ const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar;
+
+ std::string sectionIndex = CVarGetString(sidebarCvar, "");
+ if (!sidebar->contains(sectionIndex)) {
+ sectionIndex = sidebar->begin()->first;
+ }
+ float sectionCenterX = pos.x + (sidebarWidth / 2);
+ float topY = pos.y;
+ ImGui::SetNextWindowSizeConstraints({ sidebarWidth, 0 }, { sidebarWidth, columnHeight });
+ ImGui::BeginChild((menuEntries.at(headerIndex).label + " Section").c_str(), { sidebarWidth, columnHeight * 3 },
+ ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoTitleBar);
+ for (auto& sidebarLabel : menuEntries.at(headerIndex).sidebarOrder) {
+ std::string nextIndex = "";
+ UIWidgets::PushStyleButton(menuThemeIndex);
+ if (sectionIndex != sidebarLabel) {
+ ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
+ }
+ if (ModernMenuSidebarEntry(sidebarLabel)) {
+ if (headerSearch) {
+ menuSearch.Clear();
+ }
+ CVarSetString(sidebarCvar, sidebarLabel.c_str());
+ CVarSave();
+ nextIndex = sidebarLabel;
+ }
+ if (sectionIndex != sidebarLabel) {
+ ImGui::PopStyleColor();
+ }
+ UIWidgets::PopStyleButton();
+ if (nextIndex != "") {
+ sectionIndex = nextIndex;
+ }
+ }
+ ImGui::EndChild();
+
+ ImGui::PushFont(GameEngine::Instance->fontMonoLarger);
+ pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2;
+ window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 },
+ ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding);
+ pos.x += 4 + style.ItemSpacing.x;
+ ImGui::SetNextWindowPos(pos + style.ItemSpacing);
+ float sectionWidth = menuSize.x - sidebarWidth - 4 - style.ItemSpacing.x * 4;
+ std::string sectionMenuId = sectionIndex + " Settings";
+ int columns = sidebar->at(sectionIndex).columnCount;
+ size_t columnFuncs = sidebar->at(sectionIndex).columnWidgets.size();
+ if (windowWidth < 800) {
+ columns = 1;
+ }
+ float columnWidth = (sectionWidth - style.ItemSpacing.x * columns) / columns;
+ bool useColumns = columns > 1;
+ if (!useColumns || (headerSearch && menuSearchText.length() > 0)) {
+ ImGui::SameLine();
+ ImGui::SetNextWindowSizeConstraints({ sectionWidth, 0 }, { sectionWidth, columnHeight });
+ ImGui::BeginChild(sectionMenuId.c_str(), { sectionWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY,
+ ImGuiWindowFlags_NoTitleBar);
+ }
+ if (headerSearch && menuSearchText.length() > 0) {
+ uint32_t searchCount = DrawSearchResults(menuSearchText);
+ if (searchCount == 0) {
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No results found").x) / 2);
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f);
+ ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "No results found");
+ }
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Clear Search").x) / 2 - 10.0f);
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f);
+ UIWidgets::ButtonOptions clearBtnOpts = {};
+ clearBtnOpts.size = UIWidgets::Sizes::Inline;
+ if (UIWidgets::Button("Clear Search", clearBtnOpts)) {
+ menuSearch.Clear();
+ }
+
+ ImGui::EndChild();
+ } else {
+ std::string menuLabel = menuEntries.at(headerIndex).label;
+ if (MenuInit::GetUpdateFuncs().contains(menuLabel)) {
+ if (MenuInit::GetUpdateFuncs()[menuLabel].contains(sectionIndex)) {
+ for (auto& updateFunc : MenuInit::GetUpdateFuncs()[menuLabel][sectionIndex]) {
+ updateFunc();
+ }
+ }
+ }
+ for (int i = 0; i < columnFuncs; i++) {
+ std::string sectionId = fmt::format("{} Column {}", sectionMenuId, i);
+ if (useColumns) {
+ ImGui::SetNextWindowSizeConstraints({ columnWidth, 0 }, { columnWidth, columnHeight });
+ ImGui::BeginChild(sectionId.c_str(), { columnWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY,
+ ImGuiWindowFlags_NoTitleBar);
+ }
+ // for (auto& entryName : sidebar->at(sectionIndex).sidebarOrder) {
+ for (auto& entry : sidebar->at(sectionIndex).columnWidgets.at(i)) {
+ MenuDrawItem(entry, 90 / sidebar->at(sectionIndex).columnCount, menuThemeIndex);
+ }
+ //}
+ if (useColumns) {
+ ImGui::EndChild();
+ }
+ if (i < columns - 1) {
+ ImGui::SameLine();
+ }
+ }
+ }
+ if (!useColumns || menuSearchText.length() > 0) {
+ ImGui::EndChild();
+ }
+ ImGui::PopFont();
+ ImGui::PopFont();
+
+ if (!popout) {
+ ImGui::PopStyleVar();
+ }
+ ImGui::EndChild();
+ if (popout) {
+ poppedSize = ImGui::GetWindowSize();
+ poppedPos = ImGui::GetWindowPos();
+ }
+ ImGui::End();
+}
+} // namespace Ship
diff --git a/src/port/ui/Menu.h b/src/port/ui/Menu.h
new file mode 100644
index 000000000..e4aa3ac00
--- /dev/null
+++ b/src/port/ui/Menu.h
@@ -0,0 +1,66 @@
+#ifndef MENU_H
+#define MENU_H
+
+#include <libultraship/libultraship.h>
+#include "UIWidgets.h"
+#include "graphic/Fast3D/gfx_rendering_api.h"
+#include "MenuTypes.h"
+
+namespace Ship {
+uint32_t GetVectorIndexOf(std::vector<std::string>& vector, std::string value);
+class Menu : public GuiWindow {
+ public:
+ using GuiWindow::GuiWindow;
+
+ Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_ = 0,
+ UIWidgets::Colors menuThemeIndex_ = UIWidgets::Colors::LightBlue);
+ virtual ~Menu() {}
+
+ void InitElement() override;
+ void DrawElement() override;
+ void UpdateElement() override;
+ void Draw() override;
+ void InsertSidebarSearch();
+ void RemoveSidebarSearch();
+ void UpdateWindowBackendObjects();
+
+ void MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex);
+ void AddMenuEntry(std::string entryName, const char* entryCvar);
+ std::unordered_map<uint32_t, disabledInfo>& GetDisabledMap();
+
+ protected:
+ ImVec2 mOriginalSize;
+ std::string mName;
+ uint32_t mWindowFlags;
+ std::unordered_map<std::string, MainMenuEntry> menuEntries;
+ std::vector<std::string> menuOrder;
+ uint32_t DrawSearchResults(std::string& menuSearchText);
+ ImGuiTextFilter menuSearch;
+ uint8_t searchSidebarIndex;
+ UIWidgets::Colors defaultThemeIndex;
+ std::shared_ptr<std::vector<Ship::WindowBackend>> availableWindowBackends;
+ std::unordered_map<Ship::WindowBackend, const char*> availableWindowBackendsMap;
+ Ship::WindowBackend configWindowBackend;
+
+ std::unordered_map<uint32_t, disabledInfo> disabledMap;
+ std::vector<disabledInfo> disabledVector;
+ const SidebarEntry searchSidebarEntry = {
+ .columnCount = 1,
+ .columnWidgets = { { { .name = "Sidebar Search",
+ .type = WIDGET_SEARCH,
+ .options = std::make_shared<UIWidgets::WidgetOptions>(UIWidgets::WidgetOptions{}.Tooltip(
+ "Searches all menus for the given text, including tooltips.")) } } }
+ };
+ virtual void ProcessReset() {}
+
+ 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 Ship
+
+#endif // MENU_H
diff --git a/src/port/ui/MenuTypes.h b/src/port/ui/MenuTypes.h
new file mode 100644
index 000000000..72ac48846
--- /dev/null
+++ b/src/port/ui/MenuTypes.h
@@ -0,0 +1,303 @@
+#ifndef MENUTYPES_H
+#define MENUTYPES_H
+
+#include <libultraship/libultraship.h>
+#include "UIWidgets.h"
+
+typedef enum {
+ DISABLE_FOR_FREE_CAM_ON,
+ DISABLE_FOR_FREE_CAM_OFF,
+ 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_ADVANCED_RESOLUTION_ON,
+ DISABLE_FOR_VERTICAL_RES_TOGGLE_ON,
+ DISABLE_FOR_LOW_RES_MODE_ON,
+ //DISABLE_FOR_MULTIPLAYER_CONNECTED,
+} DisableOption;
+
+struct WidgetInfo;
+struct disabledInfo;
+using VoidFunc = void (*)();
+using DisableInfoFunc = bool (*)(disabledInfo&);
+using DisableVec = std::vector<DisableOption>;
+using WidgetFunc = void (*)(WidgetInfo&);
+
+typedef enum {
+ WIDGET_CHECKBOX,
+ WIDGET_COMBOBOX,
+ WIDGET_SLIDER_INT,
+ WIDGET_SLIDER_FLOAT,
+ WIDGET_CVAR_CHECKBOX,
+ WIDGET_CVAR_COMBOBOX,
+ WIDGET_CVAR_SLIDER_INT,
+ WIDGET_CVAR_SLIDER_FLOAT,
+ WIDGET_BUTTON,
+ WIDGET_COLOR_24, // color picker without alpha
+ WIDGET_COLOR_32, // color picker with alpha
+ WIDGET_SEARCH,
+ WIDGET_SEPARATOR,
+ WIDGET_SEPARATOR_TEXT,
+ WIDGET_TEXT,
+ WIDGET_WINDOW_BUTTON,
+ WIDGET_AUDIO_BACKEND, // needed for special operations that can't be handled easily with the normal combobox widget
+ WIDGET_VIDEO_BACKEND, // same as above
+ WIDGET_CUSTOM,
+} WidgetType;
+
+typedef enum {
+ SECTION_COLUMN_1,
+ SECTION_COLUMN_2,
+ SECTION_COLUMN_3,
+} SectionColumns;
+
+typedef enum {
+ 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>;
+using OptionsVariant =
+ std::variant<UIWidgets::ButtonOptions, UIWidgets::CheckboxOptions, UIWidgets::ComboboxOptions,
+ UIWidgets::FloatSliderOptions, UIWidgets::IntSliderOptions, UIWidgets::WidgetOptions>;
+
+// All the info needed for display and search of all widgets in the menu.
+// `name` is the label displayed,
+// `cVar` is the string representation of the CVar used to store the widget value
+// `tooltip` is what is displayed when hovering (except when disabled, more on that later)
+// `type` is the WidgetType for the widget, which is what determines how the information is used in the draw func
+// `options` is a variant that holds the UIWidgetsOptions struct for the widget type
+// blank objects need to be initialized with specific typing matching the expected Options struct for the widget
+// `callback` is a lambda used for running code on widget change. may need `BenGui::GetMenu()` for specific menu actions
+// `preFunc` is a lambda called before drawing code starts. It can be used to determine a widget's status,
+// whether disabled or hidden, as well as update pointers for non-CVar widget types.
+// `postFunc` is a lambda called after all drawing code is finished, for reacting to states other than
+// widgets having been changed, like holding buttons.
+// All three lambdas accept a `widgetInfo` reference in case it needs information on the widget for these operations
+// `activeDisables` is a vector of DisableOptions for specifying what reasons a widget is disabled, which are displayed
+// in the disabledTooltip for the widget. Can display multiple reasons. Handling the reasons is done in `preFunc`.
+// It is recommended to utilize `disabledInfo`/`DisableReason` to list out all reasons for disabling and isHidden so
+// the info can be shown.
+// `windowName` is what is displayed and searched for `windowButton` type and window interactions
+// `isHidden` just prevents the widget from being drawn under whatever circumstances you specify in the `preFunc`
+// `sameLine` allows for specifying that the widget should be on the same line as the previous widget
+struct WidgetInfo {
+ std::string name; // Used by all widgets
+ const char* cVar; // Used by all widgets except
+ WidgetType type;
+ std::shared_ptr<UIWidgets::WidgetOptions> options;
+ std::variant<bool*, int32_t*, float*> valuePointer;
+ WidgetFunc callback = nullptr;
+ WidgetFunc preFunc = nullptr;
+ WidgetFunc postFunc = nullptr;
+ WidgetFunc customFunction = nullptr;
+ DisableVec activeDisables = {};
+ const char* windowName = "";
+ bool isHidden = false;
+ bool sameLine = false;
+
+ WidgetInfo& CVar(const char* cVar_) {
+ cVar = cVar_;
+ return *this;
+ }
+ WidgetInfo& Options(OptionsVariant options_) {
+ switch (type) {
+ case WIDGET_AUDIO_BACKEND:
+ case WIDGET_VIDEO_BACKEND:
+ case WIDGET_COMBOBOX:
+ case WIDGET_CVAR_COMBOBOX:
+ options = std::make_shared<UIWidgets::ComboboxOptions>(std::get<UIWidgets::ComboboxOptions>(options_));
+ break;
+ case WIDGET_CHECKBOX:
+ case WIDGET_CVAR_CHECKBOX:
+ options = std::make_shared<UIWidgets::CheckboxOptions>(std::get<UIWidgets::CheckboxOptions>(options_));
+ break;
+ case WIDGET_SLIDER_FLOAT:
+ case WIDGET_CVAR_SLIDER_FLOAT:
+ options =
+ std::make_shared<UIWidgets::FloatSliderOptions>(std::get<UIWidgets::FloatSliderOptions>(options_));
+ break;
+ case WIDGET_SLIDER_INT:
+ case WIDGET_CVAR_SLIDER_INT:
+ options =
+ std::make_shared<UIWidgets::IntSliderOptions>(std::get<UIWidgets::IntSliderOptions>(options_));
+ break;
+ case WIDGET_BUTTON:
+ case WIDGET_WINDOW_BUTTON:
+ options = std::make_shared<UIWidgets::ButtonOptions>(std::get<UIWidgets::ButtonOptions>(options_));
+ break;
+ case WIDGET_CUSTOM:
+ case WIDGET_TEXT:
+ case WIDGET_SEPARATOR_TEXT:
+ case WIDGET_SEPARATOR:
+ default:
+ options = std::make_shared<UIWidgets::WidgetOptions>(std::get<UIWidgets::WidgetOptions>(options_));
+ }
+ return *this;
+ }
+ void ResetDisables() {
+ isHidden = false;
+ options->disabled = false;
+ options->disabledTooltip = "";
+ activeDisables.clear();
+ }
+ WidgetInfo& Options(std::shared_ptr<UIWidgets::WidgetOptions> options_) {
+ options = options_;
+ return *this;
+ }
+ WidgetInfo& Callback(WidgetFunc callback_) {
+ callback = callback_;
+ return *this;
+ }
+ WidgetInfo& PreFunc(WidgetFunc preFunc_) {
+ preFunc = preFunc_;
+ return *this;
+ }
+ WidgetInfo& PostFunc(WidgetFunc postFunc_) {
+ postFunc = postFunc_;
+ return *this;
+ }
+ WidgetInfo& WindowName(const char* windowName_) {
+ windowName = windowName_;
+ return *this;
+ }
+ WidgetInfo& ValuePointer(std::variant<bool*, int32_t*, float*> valuePointer_) {
+ valuePointer = valuePointer_;
+ return *this;
+ }
+ WidgetInfo& SameLine(bool sameLine_) {
+ sameLine = sameLine_;
+ return *this;
+ }
+ WidgetInfo& CustomFunction(WidgetFunc customFunction_) {
+ customFunction = customFunction_;
+ return *this;
+ }
+};
+
+struct WidgetPath {
+ std::string sectionName;
+ std::string sidebarName;
+ SectionColumns column;
+};
+
+// `disabledInfo` holds information on reasons for hiding or disabling a widget, as well as an evaluation lambda that
+// is run once per frame to update its status (this is done to prevent dozens of redundant CVarGets in each frame loop)
+// `evaluation` returns a bool which can be determined by whatever code you want that changes its status
+// `reason` is the text displayed in the disabledTooltip when a widget is disabled by a particular DisableReason
+// `active` is what's referenced when determining disabled status for a widget that uses this This can also be used to
+// hold reasons to hide widgets so that their evaluations are also only run once per frame
+struct disabledInfo {
+ DisableInfoFunc evaluation;
+ const char* reason;
+ bool active = false;
+ int32_t value = 0;
+};
+
+// struct Sidebar {
+// //std::unordered_map<std::string, SidebarEntry> entries;
+// uint32_t columnCount;
+// std::vector<std::vector<WidgetInfo>> columnWidgets;
+//
+// void Insert(std::string entryName, WidgetInfo& entry, int32_t index = -1) {
+// if (index == -1 || index >= entryOrder.size()) {
+// entryOrder.push_back(entryName);
+// } else {
+// entryOrder.insert(entryOrder.begin() + index, entryName);
+// }
+// columnWidgets[entryName].push_back({entry});
+// }
+//
+// void Erase(std::string entryName) {
+// if (columnWidgets.contains(entryName)) {
+// columnWidgets.erase(entryName);
+// }
+// std::erase_if(entryOrder, [entryName](std::string name) { return name == entryName; });
+// }
+// };
+
+// Contains the name displayed in the sidebar (label), the number of columns to use in drawing (columnCount; for visual
+// separation, 1-3), and nested vectors of the widgets, grouped by column (columnWidgets). The number of widget vectors
+// added to the column groups does not need to match the specified columnCount, e.g. you can have one vector added to
+// the sidebar, but still separate the window into 3 columns and display only in the first column
+struct SidebarEntry {
+ uint32_t columnCount;
+ std::vector<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;
+ const char* sidebarCvar;
+ std::unordered_map<std::string, SidebarEntry> sidebars = {};
+ std::vector<std::string> sidebarOrder = {};
+};
+
+static const std::unordered_map<Ship::AudioBackend, const char*> audioBackendsMap = {
+ { Ship::AudioBackend::WASAPI, "Windows Audio Session API" },
+ { Ship::AudioBackend::SDL, "SDL" },
+};
+
+static const 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" },
+};
+
+struct MenuInit {
+ static std::vector<std::function<void()>>& GetInitFuncs() {
+ static std::vector<std::function<void()>> menuInitFuncs;
+ return menuInitFuncs;
+ }
+
+ static std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::function<void()>>>>&
+ GetUpdateFuncs() {
+ static std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::function<void()>>>>
+ menuUpdateFuncs;
+ return menuUpdateFuncs;
+ }
+
+ static void InitAll() {
+ auto& menuInitFuncs = MenuInit::GetInitFuncs();
+ for (const auto& initFunc : menuInitFuncs) {
+ initFunc();
+ }
+ }
+};
+
+struct RegisterMenuInitFunc {
+ RegisterMenuInitFunc(std::function<void()> initFunc) {
+ auto& menuInitFuncs = MenuInit::GetInitFuncs();
+
+ menuInitFuncs.push_back(initFunc);
+ }
+};
+
+struct RegisterMenuUpdateFunc {
+ RegisterMenuUpdateFunc(std::function<void()> updateFunc, std::string sectionName, std::string sidebarName) {
+ auto& menuUpdateFuncs = MenuInit::GetUpdateFuncs();
+
+ menuUpdateFuncs[sectionName][sidebarName].push_back(updateFunc);
+ }
+};
+
+#endif // MENUTYPES_H
diff --git a/src/port/ui/MultiplayerWindow.cpp b/src/port/ui/MultiplayerWindow.cpp
index 0345dbaa5..51b6fbf11 100644
--- a/src/port/ui/MultiplayerWindow.cpp
+++ b/src/port/ui/MultiplayerWindow.cpp
@@ -1,5 +1,6 @@
#include "MultiplayerWindow.h"
#include "UIWidgets.h"
+#include "PortMenu.h"
#include "libultraship/src/Context.h"
#include <imgui.h>
@@ -9,7 +10,7 @@
#include <defines.h>
extern "C" {
-#include "../../../src/main.h"
+#include "main.h"
#include "networking/networking.h"
#include "actor_types.h"
#include "code_800029B0.h"
@@ -21,135 +22,145 @@ extern Camera cameras[];
extern UnkStruct_800DC5EC* D_800DC5EC;
}
+namespace GameUI {
+extern std::shared_ptr<PortMenu> mPortMenu;
namespace Multiplayer {
-MultiplayerWindow::~MultiplayerWindow() {
- SPDLOG_TRACE("destruct multiplayer window");
-}
-
-void MultiplayerWindow::InitElement() {
-}
static s32 sConnectedBool = false;
static s32 sReadyUpBool = false;
static s32 sCharacter = 0;
static s32 sCup = 0;
-void MultiplayerWindow::DrawElement() {
- const float framerate = ImGui::GetIO().Framerate;
- const float deltatime = ImGui::GetIO().DeltaTime;
- ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
+void DrawColumn1(WidgetInfo& info) {
+ if (gNetwork.isConnected) {
+ ImGui::BeginDisabled();
+ }
- if (!gNetwork.isConnected) {
+ static char username[NETWORK_USERNAME_LENGTH] = "TestUser";
- static char username[NETWORK_USERNAME_LENGTH] = "TestUser";
+ ImGui::Text("Username:");
+ ImGui::InputText("##edit", username, IM_ARRAYSIZE(username));
- ImGui::Text("Username:");
- ImGui::InputText("##edit", username, IM_ARRAYSIZE(username));
+ static char address[32] = "127.0.0.1:64010";
+ static char addressCopy[32];
+ strncpy(addressCopy, address, sizeof(addressCopy));
+ addressCopy[sizeof(addressCopy) - 1] = '\0'; // Ensure null termination
- static char address[32] = "127.0.0.1:64010";
- static char addressCopy[32];
- strncpy(addressCopy, address, sizeof(addressCopy));
- addressCopy[sizeof(addressCopy) - 1] = '\0'; // Ensure null termination
+ ImGui::Text("Host:");
+ ImGui::InputText("##edit3", addressCopy, IM_ARRAYSIZE(addressCopy));
- ImGui::Text("Host:");
- ImGui::InputText("##edit3", addressCopy, IM_ARRAYSIZE(addressCopy));
+ if (ImGui::Button("Connect!")) {
+ char* ip = NULL;
+ uint16_t port = 0;
- if (ImGui::Button("Connect!")) {
- char* ip = NULL;
- uint16_t port = 0;
+ char* token = strtok(addressCopy, ":");
+ ip = token;
- char* token = strtok(addressCopy, ":");
- ip = token;
+ token = strtok(NULL, ":");
+ if (token != NULL) {
+ port = (uint16_t) atoi(token);
+ }
- token = strtok(NULL, ":");
- if (token != NULL) {
- port = (uint16_t) atoi(token);
- }
+ gNetwork.enabled = true;
+ ConnectToServer(ip, port, username);
+ }
+ if (gNetwork.isConnected) {
+ ImGui::EndDisabled();
+ }
+}
- gNetwork.enabled = true;
- ConnectToServer(ip, port, username);
- }
+void DrawColumn2(WidgetInfo& info) {
+ if (!gNetwork.isConnected) {
+ ImGui::BeginDisabled();
+ }
- } else {
+ ImGui::Text("Select Character:");
+ ImGui::Spacing();
- ImGui::Spacing();
- ImGui::Text("Select Character:");
- ImGui::Spacing();
-
- for (size_t i = 0; i < NUM_PLAYERS; i++) {
- // These are euc-jp characters that look sort of like a hyphen -
- if (D_800E76A8[i] == NULL || D_800E76A8[i] == "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC") {
- break;
- }
- ImGui::SameLine();
- if (ImGui::Button(D_800E76A8[i])) {
- network_character_vote(i);
- sCharacter = i;
- }
+ for (size_t i = 0; i < NUM_PLAYERS; i++) {
+ // These are euc-jp characters that look sort of like a hyphen -
+ if (D_800E76A8[i] == NULL || D_800E76A8[i] == "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC") {
+ break;
}
- ImGui::Spacing();
- ImGui::Text("-- %s --", D_800E76A8[sCharacter]);
-
- ImGui::Spacing();
- ImGui::Spacing();
- ImGui::Text("Course Vote:");
-
- for (size_t i = 0; i < NUM_CUPS - 1; i++) {
- if (ImGui::Button(gCupNames[i])) {
- network_cup_vote(i);
- sCup = i;
- }
- ImGui::SameLine();
+ ImGui::SameLine();
+ if (ImGui::Button(D_800E76A8[i])) {
+ network_character_vote(i);
+ sCharacter = i;
}
- ImGui::Spacing();
- ImGui::Text("-- %s --", gCupNames[sCup]);
+ }
+ ImGui::Spacing();
+ ImGui::Text("-- %s --", D_800E76A8[sCharacter]);
- if (ImGui::Button("test", ImVec2(-1, 0))) {
- // Add disconnect logic here
- cameras[0].playerId = 2;
- D_800DC5EC->player = &gPlayers[2];
- }
+ ImGui::Spacing();
+ ImGui::Spacing();
+ ImGui::Text("Course Vote:");
- for (size_t i = 0; i < NETWORK_MAX_PLAYERS; i++) {
- ImGui::Text("Slot %d: char: %d, hasAuthority: %d", clients[i].slot, clients[i].character,
- gPlayers[i].nHasAuthority);
+ for (size_t i = 0; i < NUM_CUPS - 1; i++) {
+ if (ImGui::Button(gCupNames[i])) {
+ network_cup_vote(i);
+ sCup = i;
}
+ ImGui::SameLine();
+ }
+ ImGui::Spacing();
+ ImGui::Text("-- %s --", gCupNames[sCup]);
- /* Stick UI to the bottom of the panel */
+ if (ImGui::Button("test", ImVec2(-1, 0))) {
+ // Add disconnect logic here
+ cameras[0].playerId = 2;
+ D_800DC5EC->player = &gPlayers[2];
+ }
- float windowHeight = ImGui::GetWindowHeight();
+ for (size_t i = 0; i < NETWORK_MAX_PLAYERS; i++) {
+ ImGui::Text("Slot %d: char: %d, hasAuthority: %d", clients[i].slot, clients[i].character,
+ gPlayers[i].nHasAuthority);
+ }
- // Move the cursor to a position relative to the bottom of the window
- ImGui::SetCursorPosY(windowHeight -
- ImGui::GetFrameHeightWithSpacing() * 2); // Adjust Y position for two buttons
+ /* Stick UI to the bottom of the panel */
- char buttonLabel[32];
+ float windowHeight = ImGui::GetWindowHeight();
- if (sReadyUpBool) {
- strcpy(buttonLabel, "Lets Go!");
- } else {
- strcpy(buttonLabel, "Ready Up!");
- }
+ // Move the cursor to a position relative to the bottom of the window
+ ImGui::SetCursorPosY(windowHeight -
+ ImGui::GetFrameHeightWithSpacing() * 2); // Adjust Y position for two buttons
- // First button (Ready Up / Let's Go)
- if (ImGui::Button(buttonLabel, ImVec2(-1, 0))) {
- sReadyUpBool = !sReadyUpBool;
- networking_ready_up(sReadyUpBool);
- }
+ char buttonLabel[32];
- // Add some vertical spacing between the buttons
- ImGui::Spacing();
+ if (sReadyUpBool) {
+ strcpy(buttonLabel, "Lets Go!");
+ } else {
+ strcpy(buttonLabel, "Ready Up!");
+ }
- // Second button (Disconnect)
- if (ImGui::Button("Disconnect", ImVec2(-1, 0))) {
- // Add disconnect logic here
- networking_disconnect();
- }
+ // First button (Ready Up / Let's Go)
+ if (ImGui::Button(buttonLabel, ImVec2(-1, 0))) {
+ sReadyUpBool = !sReadyUpBool;
+ networking_ready_up(sReadyUpBool);
}
- ImGui::PopStyleColor();
+ // Add some vertical spacing between the buttons
+ ImGui::Spacing();
+
+ // Second button (Disconnect)
+ if (ImGui::Button("Disconnect", ImVec2(-1, 0))) {
+ // Add disconnect logic here
+ networking_disconnect();
+ }
+ if (!gNetwork.isConnected) {
+ ImGui::EndDisabled();
+ }
}
-void MultiplayerWindow::UpdateElement() {
+void RegisterMultiplayerWidgets() {
+ mPortMenu->AddSidebarEntry("Enhancements", "Multiplayer", 2);
+ WidgetPath path = { "Enhancements", "Multiplayer", SECTION_COLUMN_1 };
+ mPortMenu->AddWidget(path, "Multi Custom 1", WIDGET_CUSTOM)
+ .CustomFunction(DrawColumn1);
+ path.column = SECTION_COLUMN_2;
+ mPortMenu->AddWidget(path, "Multi Custom 2", WIDGET_CUSTOM)
+ .CustomFunction(DrawColumn2);
}
+
+static RegisterMenuInitFunc initFunc(RegisterMultiplayerWidgets);
} // namespace Multiplayer
+} // namespace GameUI
diff --git a/src/port/ui/MultiplayerWindow.h b/src/port/ui/MultiplayerWindow.h
index aad42ba2b..d196c33c0 100644
--- a/src/port/ui/MultiplayerWindow.h
+++ b/src/port/ui/MultiplayerWindow.h
@@ -3,14 +3,4 @@
#include <libultraship/libultraship.h>
namespace Multiplayer {
-class MultiplayerWindow : public Ship::GuiWindow {
- public:
- using Ship::GuiWindow::GuiWindow;
- ~MultiplayerWindow();
-
- private:
- void InitElement() override;
- void DrawElement() override;
- void UpdateElement() override;
-};
} // namespace Multiplayer
diff --git a/src/port/ui/PortMenu.cpp b/src/port/ui/PortMenu.cpp
new file mode 100644
index 000000000..b1e9cfde2
--- /dev/null
+++ b/src/port/ui/PortMenu.cpp
@@ -0,0 +1,497 @@
+#include "PortMenu.h"
+#include "UIWidgets.h"
+#include "port/Game.h"
+#include "window/gui/GuiMenuBar.h"
+#include "window/gui/GuiElement.h"
+#include <variant>
+#include "StringHelper.h"
+#include <spdlog/fmt/fmt.h>
+#include <variant>
+#include <tuple>
+#include "ResolutionEditor.h"
+
+extern "C" {
+ extern s32 gGamestateNext;
+ extern s32 gMenuSelection;
+ #include "audio/external.h"
+ #include "defines.h"
+}
+
+namespace GameUI {
+extern std::shared_ptr<PortMenu> mPortMenu;
+
+using namespace UIWidgets;
+
+void PortMenu::AddSidebarEntry(std::string sectionName, std::string sidebarName, uint32_t columnCount) {
+ assert(!sectionName.empty());
+ assert(!sidebarName.empty());
+ menuEntries.at(sectionName).sidebars.emplace(sidebarName, SidebarEntry{ .columnCount = columnCount });
+ menuEntries.at(sectionName).sidebarOrder.push_back(sidebarName);
+}
+
+WidgetInfo& PortMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType) {
+ assert(!widgetName.empty()); // Must be unique
+ assert(menuEntries.contains(pathInfo.sectionName)); // Section/header must already exist
+ assert(menuEntries.at(pathInfo.sectionName).sidebars.contains(pathInfo.sidebarName)); // Sidebar must already exist
+ std::unordered_map<std::string, SidebarEntry>& sidebar = menuEntries.at(pathInfo.sectionName).sidebars;
+ uint8_t column = pathInfo.column;
+ if (sidebar.contains(pathInfo.sidebarName)) {
+ while (sidebar.at(pathInfo.sidebarName).columnWidgets.size() < column + 1) {
+ sidebar.at(pathInfo.sidebarName).columnWidgets.push_back({});
+ }
+ }
+ SidebarEntry& entry = sidebar.at(pathInfo.sidebarName);
+ entry.columnWidgets.at(column).push_back({ .name = widgetName, .type = widgetType });
+ WidgetInfo& widget = entry.columnWidgets.at(column).back();
+ switch (widgetType) {
+ case WIDGET_CHECKBOX:
+ case WIDGET_CVAR_CHECKBOX:
+ widget.options = std::make_shared<CheckboxOptions>();
+ break;
+ case WIDGET_SLIDER_FLOAT:
+ case WIDGET_CVAR_SLIDER_FLOAT:
+ widget.options = std::make_shared<FloatSliderOptions>();
+ break;
+ case WIDGET_SLIDER_INT:
+ case WIDGET_CVAR_SLIDER_INT:
+ widget.options = std::make_shared<IntSliderOptions>();
+ break;
+ case WIDGET_COMBOBOX:
+ case WIDGET_CVAR_COMBOBOX:
+ case WIDGET_AUDIO_BACKEND:
+ case WIDGET_VIDEO_BACKEND:
+ widget.options = std::make_shared<ComboboxOptions>();
+ break;
+ case WIDGET_BUTTON:
+ widget.options = std::make_shared<ButtonOptions>();
+ break;
+ case WIDGET_WINDOW_BUTTON:
+ widget.options = std::make_shared<ButtonOptions>(ButtonOptions{ .size = Sizes::Inline });
+ break;
+ case WIDGET_COLOR_24:
+ case WIDGET_COLOR_32:
+ break;
+ case WIDGET_SEARCH:
+ case WIDGET_SEPARATOR:
+ case WIDGET_SEPARATOR_TEXT:
+ case WIDGET_TEXT:
+ default:
+ widget.options = std::make_shared<WidgetOptions>();
+ }
+ return widget;
+}
+
+void PortMenu::AddSettings() {
+ // Add Settings menu
+ AddMenuEntry("Settings", "gSettings.Menu.SettingsSidebarSection");
+ // General Settings
+ AddSidebarEntry("Settings", "General", 3);
+ WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 };
+ AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX)
+ .CVar("gSettings.Menu.Theme")
+ .Options(ComboboxOptions()
+ .Tooltip("Changes the Theme of the Menu Widgets.")
+ .ComboMap(menuThemeOptions)
+ .DefaultIndex(Colors::LightBlue));
+#if not defined(__SWITCH__) and not defined(__WIIU__)
+ AddWidget(path, "Menu Controller Navigation", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_IMGUI_CONTROLLER_NAV)
+ .Options(CheckboxOptions().Tooltip(
+ "Allows controller navigation of the Spaghetti menu (Settings, Enhancements,...)\nCAUTION: "
+ "This will disable game inputs while the menu is visible.\n\nD-pad to move between "
+ "items, A to select, B to move up in scope."));
+ AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX)
+ .CVar("gSettings.CursorVisibility")
+ .Callback([](WidgetInfo& info) {
+ Ship::Context::GetInstance()->GetWindow()->SetForceCursorVisibility(
+ CVarGetInteger("gSettings.CursorVisibility", 0));
+ })
+ .Options(CheckboxOptions().Tooltip("Makes the cursor always visible, even in full screen."));
+#endif
+ AddWidget(path, "Search In Sidebar", WIDGET_CVAR_CHECKBOX)
+ .CVar("gSettings.Menu.SidebarSearch")
+ .Callback([](WidgetInfo& info) {
+ if (CVarGetInteger("gSettings.Menu.SidebarSearch", 0)) {
+ mPortMenu->InsertSidebarSearch();
+ } else {
+ mPortMenu->RemoveSidebarSearch();
+ }
+ })
+ .Options(CheckboxOptions().Tooltip(
+ "Displays the Search menu as a sidebar entry in Settings instead of in the header."));
+ AddWidget(path, "Search Input Autofocus", WIDGET_CVAR_CHECKBOX)
+ .CVar("gSettings.Menu.SearchAutofocus")
+ .Options(CheckboxOptions().Tooltip(
+ "Search input box gets autofocus when visible. Does not affect using other widgets."));
+ AddWidget(path, "Alt Assets Tab hotkey", WIDGET_CVAR_CHECKBOX)
+ .CVar("gEnhancements.Mods.AlternateAssetsHotkey")
+ .Options(
+ CheckboxOptions().Tooltip("Allows pressing the Tab key to toggle alternate assets").DefaultValue(true));
+ AddWidget(path, "Open App Files Folder", WIDGET_BUTTON)
+ .Callback([](WidgetInfo& info) {
+ std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath();
+ SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str());
+ })
+ .Options(ButtonOptions().Tooltip("Opens the folder that contains the save and mods folders, etc."));
+
+ // Audio Settings
+ path.sidebarName = "Audio";
+ AddSidebarEntry("Settings", "Audio", 3);
+ AddWidget(path, "Master Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gGameMasterVolume")
+ .Options(FloatSliderOptions()
+ .Tooltip("Adjust the overall sound volume.")
+ .ShowButtons(false)
+ .Format("")
+ .IsPercentage());
+ AddWidget(path, "Main Music Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gMainMusicVolume")
+ .Callback([](WidgetInfo& info) {
+ audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f));
+ })
+ .Options(FloatSliderOptions()
+ .Tooltip("Adjust the background music volume.")
+ .ShowButtons(false)
+ .Format("")
+ .IsPercentage());
+ AddWidget(path, "Sound Effects Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gSFXMusicVolume")
+ .Callback([](WidgetInfo& info) {
+ audio_set_player_volume(SEQ_PLAYER_SFX, CVarGetFloat("gSFXMusicVolume", 1.0f));
+ })
+ .Options(
+ FloatSliderOptions().Tooltip("Adjust the sound effects volume.").ShowButtons(false).Format("").IsPercentage());
+ AddWidget(path, "Sound Effects Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gEnvironmentVolume")
+ .Callback([](WidgetInfo& info) {
+ audio_set_player_volume(SEQ_PLAYER_ENV, CVarGetFloat("gEnvironmentVolume", 1.0f));
+ })
+ .Options(FloatSliderOptions()
+ .Tooltip("Adjust the environment volume.")
+ .ShowButtons(false)
+ .Format("")
+ .IsPercentage());
+ AddWidget(path, "Audio API", WIDGET_AUDIO_BACKEND);
+
+ // Graphics Settings
+ static int32_t maxFps;
+ static bool isFullscreen = false;
+ const char* tooltip = "";
+ if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
+ maxFps = 360;
+ tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is "
+ "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target "
+ "FPS than your monitor's refresh rate will waste resources, and might give a worse result.";
+ } else {
+ maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
+ tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is "
+ "purely visual and does not impact game logic, execution of glitches etc.";
+ }
+ path.sidebarName = "Graphics";
+ AddSidebarEntry("Settings", "Graphics", 3);
+ AddWidget(path, "Toggle Fullscreen", WIDGET_CHECKBOX)
+ .ValuePointer(&isFullscreen)
+ .PreFunc([](WidgetInfo& info) { isFullscreen = Ship::Context::GetInstance()->GetWindow()->IsFullscreen(); })
+ .Callback([](WidgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); })
+ .Options(CheckboxOptions().Tooltip("Toggles Fullscreen On/Off."));
+#ifndef __APPLE__
+ AddWidget(path, "Internal Resolution: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar(CVAR_INTERNAL_RESOLUTION)
+ .Callback([](WidgetInfo& info) {
+ Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(
+ CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1));
+ })
+ .PreFunc([](WidgetInfo& info) {
+ if (mPortMenu->disabledMap.at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active &&
+ mPortMenu->disabledMap.at(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON).active) {
+ info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_ON);
+ info.activeDisables.push_back(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON);
+ } else if (mPortMenu->disabledMap.at(DISABLE_FOR_LOW_RES_MODE_ON).active) {
+ info.activeDisables.push_back(DISABLE_FOR_LOW_RES_MODE_ON);
+ }
+ })
+ .Options(
+ FloatSliderOptions()
+ .Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective "
+ "form of anti-aliasing.")
+ .ShowButtons(false)
+ .IsPercentage()
+ .Format("")
+ .Min(0.5f)
+ .Max(2.0f));
+#endif
+#ifndef __WIIU__
+ AddWidget(path, "Anti-aliasing (MSAA): %d", WIDGET_CVAR_SLIDER_INT)
+ .CVar(CVAR_MSAA_VALUE)
+ .Callback([](WidgetInfo& info) {
+ Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1));
+ })
+ .Options(
+ IntSliderOptions()
+ .Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of "
+ "rendered geometry.\n"
+ "Higher sample count will result in smoother edges on models, but may reduce performance.")
+ .Min(1)
+ .Max(8)
+ .DefaultValue(1));
+#endif
+
+ AddWidget(path, "Current FPS: %d", WIDGET_CVAR_SLIDER_INT)
+ .CVar("gInterpolationFPS")
+ .Callback([](WidgetInfo& info) {
+ int32_t defaultValue = std::static_pointer_cast<IntSliderOptions>(info.options)->defaultValue;
+ if (CVarGetInteger(info.cVar, defaultValue) == defaultValue) {
+ info.name = "Current FPS: Original (%d)";
+ } else {
+ info.name = "Current FPS: %d";
+ }
+ })
+ .PreFunc([](WidgetInfo& info) {
+ if (mPortMenu->disabledMap.at(DISABLE_FOR_MATCH_REFRESH_RATE_ON).active)
+ info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON);
+ })
+ .Options(IntSliderOptions().Tooltip(tooltip).Min(20).Max(maxFps).DefaultValue(20));
+ AddWidget(path, "Match Refresh Rate", WIDGET_BUTTON)
+ .Callback([](WidgetInfo& info) {
+ int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate();
+ if (hz >= 20 && hz <= 360) {
+ CVarSetInteger("gInterpolationFPS", hz);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ }
+ })
+ .PreFunc([](WidgetInfo& info) { info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; })
+ .Options(ButtonOptions().Tooltip("Matches interpolation value to the current game's window refresh rate."));
+ AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX)
+ .CVar("gMatchRefreshRate")
+ .PreFunc([](WidgetInfo& info) { info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_DIRECTX).active; })
+ .Options(CheckboxOptions().Tooltip("Matches interpolation value to the current game's window refresh rate."));
+ AddWidget(path, "Jitter fix : >= % d FPS", WIDGET_CVAR_SLIDER_INT)
+ .CVar("gExtraLatencyThreshold")
+ .PreFunc([](WidgetInfo& info) { info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; })
+ .Options(IntSliderOptions()
+ .Tooltip("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.")
+ .Min(0)
+ .Max(360)
+ .DefaultValue(80));
+ AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND);
+ AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_VSYNC_ENABLED)
+ .PreFunc([](WidgetInfo& info) { info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; })
+ .Options(CheckboxOptions().Tooltip("Enables Vsync."));
+ AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_SDL_WINDOWED_FULLSCREEN)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_NO_WINDOWED_FULLSCREEN).active;
+ })
+ .Options(CheckboxOptions().Tooltip("Enables Windowed Fullscreen Mode."));
+ AddWidget(path, "Allow multi-windows", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_ENABLE_MULTI_VIEWPORTS)
+ .PreFunc(
+ [](WidgetInfo& info) { info.isHidden = mPortMenu->disabledMap.at(DISABLE_FOR_NO_MULTI_VIEWPORT).active; })
+ .Options(CheckboxOptions().Tooltip(
+ "Allows multiple windows to be opened at once. Requires a reload to take effect."));
+ AddWidget(path, "Texture Filter (Needs reload)", WIDGET_CVAR_COMBOBOX)
+ .CVar(CVAR_TEXTURE_FILTER)
+ .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap));
+
+ path.sidebarName = "Controls";
+ AddSidebarEntry("Settings", "Controls", 1);
+ AddWidget(path,
+ "This interface can be a little daunting. Please bear with us as we work to improve the experience "
+ "and address some known issues.\n"
+ "\n"
+ "At first glance, you may notice several input devices displayed below the 'Clear All' button. "
+ "Some of these might be other controllers connected to your computer, while others may be "
+ "duplicated controllers (a known issue). We recommend clicking on the box with the " ICON_FA_EYE
+ " icon and the name of any disconnected or unused controllers to hide their inputs. Make sure the "
+ "target controller remains visible.\n"
+ "\n"
+ "If you encounter issues connecting your controller or registering inputs, try closing Steam or "
+ "any other external input software. Alternatively, test a different controller to determine if "
+ "it's a compatibility issue.\n",
+ WIDGET_TEXT);
+ AddWidget(path, "Bindings", WIDGET_SEPARATOR_TEXT);
+ AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON)
+ .CVar(CVAR_CONTROLLER_CONFIGURATION_WINDOW_OPEN)
+ .WindowName("Input Editor")
+ .Options(ButtonOptions().Tooltip("Enables the separate Bindings Window.").Size(Sizes::Inline));
+}
+int32_t motionBlurStrength;
+
+void PortMenu::AddEnhancements() {
+ AddMenuEntry("Enhancements", "gSettings.Menu.EnhancementsSidebarSection");
+ WidgetPath path = { "Enhancements", "General", SECTION_COLUMN_1 };
+ AddSidebarEntry("Enhancements", "General", 3);
+ //UIWidgets::WindowButton("Multiplayer", "gMultiplayerWindowEnabled", GameUI::mMultiplayerWindow,
+ // { .tooltip = "Shows the multiplayer window" });
+ // UIWidgets::WindowButton("Freecam", "gFreecam", GameUI::mFreecamWindow,
+ // { .tooltip = "Allows you to fly around the course" });
+ AddWidget(path, "No multiplayer feature cuts", WIDGET_CVAR_CHECKBOX)
+ .CVar("gMultiplayerNoFeatureCuts")
+ .Options(CheckboxOptions().Tooltip("Allows full train and jumbotron in multiplayer, etc."));
+ AddWidget(path, "General Improvements", WIDGET_CVAR_CHECKBOX)
+ .CVar("gImprovements").Options(CheckboxOptions().Tooltip("General improvements to the game experience."));
+ AddWidget(path, "No Level of Detail (LOD)", WIDGET_CVAR_CHECKBOX)
+ .CVar("gDisableLod")
+ .Options(CheckboxOptions().Tooltip("Disable Level of Detail (LOD) to avoid models using lower poly versions at a distance"));
+ AddWidget(path, "Disable Culling", WIDGET_CVAR_CHECKBOX)
+ .CVar("gNoCulling").Options(CheckboxOptions().Tooltip("Disable original culling of mk64"));
+ AddWidget(path, "Far Frustrum", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gFarFrustrum")
+ .Options(FloatSliderOptions().Min(0.0f).Max(10000.0f).DefaultValue(10000.0f)
+ .Tooltip("Say how Far the Frustrum are when 'Disable Culling' are enable").Step(10.0f));
+
+ path = { "Enhancements", "Cheats", SECTION_COLUMN_1 };
+ AddSidebarEntry("Enhancements", "Cheats", 3);
+ AddWidget(path, "Moon Jump", WIDGET_CVAR_CHECKBOX)
+ .CVar("gEnableMoonJump");
+ AddWidget(path, "Enable Custom CC", WIDGET_CVAR_CHECKBOX)
+ .CVar("gEnableCustomCC");
+ AddWidget(path, "Custom CC", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gCustomCC")
+ .Options(FloatSliderOptions().Min(0.0f).Max(1000.0f).DefaultValue(150.0f).Step(10.0f));
+ AddWidget(path, "Disable Wall Collision", WIDGET_CVAR_CHECKBOX)
+ .CVar("gNoWallColision")
+ .Options(CheckboxOptions().Tooltip("Disable wall collision."));
+ AddWidget(path, "Min Height", WIDGET_CVAR_SLIDER_FLOAT)
+ .CVar("gMinHeight")
+ .Options(FloatSliderOptions().Min(-50.0f).Max(50.0f).DefaultValue(0.0f)
+ .Tooltip("When Disable Wall Collision are enable what is the minimal height you can get."));
+}
+
+void PortMenu::AddDevTools() {
+ AddMenuEntry("Developer", "gSettings.Menu.DevToolsSidebarSection");
+ AddSidebarEntry("Developer", "General", 3);
+ WidgetPath path = { "Developer", "General", SECTION_COLUMN_1 };
+ AddWidget(path, "Popout Menu", WIDGET_CVAR_CHECKBOX)
+ .CVar("gSettings.Menu.Popout")
+ .Options(CheckboxOptions().Tooltip("Changes the menu display from overlay to windowed."));
+ AddWidget(path, "Debug Mode", WIDGET_CVAR_CHECKBOX)
+ .CVar("gEnableDebugMode")
+ .Options(CheckboxOptions().Tooltip("Enables Debug Mode."));
+ AddWidget(path, "Render Collision", WIDGET_CVAR_CHECKBOX)
+ .CVar("gRenderCollisionMesh")
+ .Options(CheckboxOptions().Tooltip("Renders the collision mesh instead of the course mesh"));
+
+ path = { "Developer", "Gfx Debugger", SECTION_COLUMN_1 };
+ AddSidebarEntry("Developer", "Gfx Debugger", 1);
+ AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON)
+ .CVar("gGfxDebuggerEnabled")
+ .Options(ButtonOptions().Tooltip(
+ "Enables the Gfx Debugger window, allowing you to input commands. Type help for some examples"))
+ .WindowName("GfxDebuggerWindow");
+
+ path = { "Developer", "Game Info", SECTION_COLUMN_1 };
+ AddSidebarEntry("Developer", "Game Info", 1);
+ AddWidget(path, "Popout Game Info", WIDGET_WINDOW_BUTTON)
+ .CVar("gGameInfoEnabled")
+ .Options(ButtonOptions().Tooltip(
+ "Shows the game info window, contains player and actor information"))
+ .WindowName("GfxDebuggerWindow");
+
+ path = { "Developer", "Stats", SECTION_COLUMN_1 };
+ AddSidebarEntry("Developer", "Stats", 1);
+ AddWidget(path, "Popout Stats", WIDGET_WINDOW_BUTTON)
+ .CVar("gStatsEnabled")
+ .Options(ButtonOptions().Tooltip("Shows the stats window, with your FPS and frametimes, and the OS you're playing on"))
+ .WindowName("Stats");
+
+ path = { "Developer", "Console", SECTION_COLUMN_1 };
+ AddSidebarEntry("Developer", "Console", 1);
+ AddWidget(path, "Popout Console", WIDGET_WINDOW_BUTTON)
+ .CVar("gConsoleEnabled")
+ .Options(ButtonOptions().Tooltip(
+ "Enables the console window, allowing you to input commands. Type help for some examples"))
+ .WindowName("Console");
+}
+
+PortMenu::PortMenu(const std::string& consoleVariable, const std::string& name)
+ : Menu(consoleVariable, name, 0, UIWidgets::Colors::LightBlue) {
+}
+
+//bool CheckNetworkConnected(disabledInfo& info) {
+// return gNetwork.isConnected;
+//}
+
+void PortMenu::InitElement() {
+ Ship::Menu::InitElement();
+ AddSettings();
+ AddEnhancements();
+ AddDevTools();
+
+ if (CVarGetInteger("gSettings.Menu.SidebarSearch", 0)) {
+ InsertSidebarSearch();
+ }
+
+ for (auto& initFunc : MenuInit::GetInitFuncs()) {
+ initFunc();
+ }
+
+ disabledMap = {
+ { DISABLE_FOR_FREE_CAM_ON,
+ { [](disabledInfo& info) -> bool { return CVarGetInteger("gFreecam", 0); }, "Free Cam is Enabled" } },
+ { DISABLE_FOR_FREE_CAM_OFF,
+ { [](disabledInfo& info) -> bool { return !CVarGetInteger("gFreecam", 0); }, "Free Cam is Disabled" } },
+ { DISABLE_FOR_DEBUG_MODE_OFF,
+ { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnableDebugMode", 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_ADVANCED_RESOLUTION_ON,
+ { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); },
+ "Advanced Resolution Enabled" } },
+ { DISABLE_FOR_VERTICAL_RES_TOGGLE_ON,
+ { [](disabledInfo& info) -> bool {
+ return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0);
+ },
+ "Vertical Resolution Toggle Enabled" } },
+ { DISABLE_FOR_LOW_RES_MODE_ON,
+ { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_LOW_RES_MODE, 0); }, "N64 Mode Enabled" } },
+ //{ DISABLE_FOR_MULTIPLAYER_CONNECTED,
+ // { CheckNetworkConnected, "Multiplayer Connected"}},
+ };
+}
+
+void PortMenu::UpdateElement() {
+ Ship::Menu::UpdateElement();
+}
+
+void PortMenu::Draw() {
+ Ship::Menu::Draw();
+}
+
+void PortMenu::DrawElement() {
+ Ship::Menu::DrawElement();
+}
+} // namespace BenGui
diff --git a/src/port/ui/PortMenu.h b/src/port/ui/PortMenu.h
new file mode 100644
index 000000000..209166a9e
--- /dev/null
+++ b/src/port/ui/PortMenu.h
@@ -0,0 +1,64 @@
+#ifndef PORTMENU_H
+#define PORTMENU_H
+
+#include <libultraship/libultraship.h>
+#include "UIWidgets.h"
+#include "Menu.h"
+#include "graphic/Fast3D/gfx_rendering_api.h"
+
+namespace GameUI {
+
+static const std::unordered_map<int32_t, const char*> menuThemeOptions = {
+ { UIWidgets::Colors::Red, "Red" },
+ { UIWidgets::Colors::DarkRed, "Dark Red" },
+ { UIWidgets::Colors::Orange, "Orange" },
+ { UIWidgets::Colors::Green, "Green" },
+ { UIWidgets::Colors::DarkGreen, "Dark Green" },
+ { UIWidgets::Colors::LightBlue, "Light Blue" },
+ { UIWidgets::Colors::Blue, "Blue" },
+ { UIWidgets::Colors::DarkBlue, "Dark Blue" },
+ { UIWidgets::Colors::Indigo, "Indigo" },
+ { UIWidgets::Colors::Violet, "Violet" },
+ { UIWidgets::Colors::Purple, "Purple" },
+ { UIWidgets::Colors::Brown, "Brown" },
+ { UIWidgets::Colors::Gray, "Gray" },
+ { UIWidgets::Colors::DarkGray, "Dark Gray" },
+};
+
+static const std::unordered_map<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*> 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" },
+};
+
+class PortMenu : public Ship::Menu {
+ public:
+ PortMenu(const std::string& consoleVariable, const std::string& name);
+ ~PortMenu() {}
+
+ void InitElement() override;
+ void DrawElement() override;
+ void UpdateElement() override;
+ void Draw() override;
+
+ void AddSidebarEntry(std::string sectionName, std::string sidbarName, uint32_t columnCount);
+ WidgetInfo& AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType);
+ void AddSettings();
+ void AddEnhancements();
+ void AddDevTools();
+};
+} // namespace BenGui
+
+#endif // PORTMENU_H
diff --git a/src/port/ui/ResolutionEditor.cpp b/src/port/ui/ResolutionEditor.cpp
index dbc9259d7..c1fa6f263 100644
--- a/src/port/ui/ResolutionEditor.cpp
+++ b/src/port/ui/ResolutionEditor.cpp
@@ -1,422 +1,542 @@
#include "ResolutionEditor.h"
-#include "UIWidgets.h"
-#include "libultraship/src/Context.h"
-
#include <imgui.h>
#include <libultraship/libultraship.h>
+
+#include "UIWidgets.h"
#include <graphic/Fast3D/gfx_pc.h>
+#include "port/Engine.h"
+#include "PortMenu.h"
-/* Console Variables are grouped under gAdvancedResolution. (e.g. "gAdvancedResolution.Enabled")
+/* Console Variables are grouped under gAdvancedResolution. (e.g. CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled")
- The following CVars are used in Libultraship and can be edited here:
+ The following cvars are used in Libultraship and can be edited here:
- Enabled - Turns Advanced Resolution Mode on.
- AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero.
- VerticalPixelCount, VerticalResolutionToggle - Resolution controls.
- PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls.
- (Waiting on a second PR merge on LUS for this to fully function.):
- IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode.
- IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds.
- The following CVars are also implemented in LUS for niche use cases:
+ The following cvars are also implemented in LUS for niche use cases:
- IgnoreAspectCorrection - Stretch framebuffer to fill screen.
This is something of a power-user setting for niche setups that most people won't need or care about,
but may be useful if playing the Switch/Wii U ports on a 4:3 television.
- IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1.
- This isn't that useful at the moment.
+ This isn't that useful at the moment, so it's unused here.
*/
-namespace AdvancedResolutionSettings {
+namespace GameUI {
+extern std::shared_ptr<PortMenu> mPortMenu;
enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount };
-const char* aspectRatioPresetLabels[] = {
- "Off", "Custom", "Original (4:3)", "Widescreen (16:9)", "Nintendo 3DS (5:3)", "16:10 (8:5)", "Ultrawide (21:9)"
-};
-const float aspectRatioPresetsX[] = { 0.0f, 12.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f };
+std::unordered_map<int32_t, const char*> aspectRatioPresetLabels = { { 0, "Off" },
+ { 1, "Custom" },
+ { 2, "Original (4:3)" },
+ { 3, "Widescreen (16:9)" },
+ { 4, "Nintendo 3DS (5:3)" },
+ { 5, "16:10 (8:5)" },
+ { 6, "Ultrawide (21:9)" } };
+const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f };
const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f };
const int default_aspectRatio = 1; // Default combo list option
const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)",
"5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" };
-const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160, 480 };
+const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 };
const int default_pixelCount = 0; // Default combo list option
-const uint32_t minVerticalPixelCount = 240; // see: Ship::AdvancedResolution()
-const uint32_t maxVerticalPixelCount = 4320;
+// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges()
+const uint32_t minVerticalPixelCount = SCREEN_HEIGHT;
+const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution
const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider.
-const float enhancementSpacerHeight = 19.0f;
-// This will need to be determined more intelligently when Hi-DPI UI support is added.
-
-void AdvancedResolutionSettingsWindow::InitElement() {
-}
-
-void AdvancedResolutionSettingsWindow::DrawElement() {
- ImGui::SetNextWindowSize(ImVec2(497, 532), ImGuiCond_FirstUseEver);
- if (ImGui::Begin("Advanced Resolution Settings", &mIsVisible)) {
- // Initialise update flags.
- bool update[sizeof(setting)];
- for (unsigned short i = 0; i < sizeof(setting); i++)
- update[i] = false;
- static short updateCountdown = 0;
- short countdownStartingValue = CVarGetInteger("gInterpolationFPS", 30) / 2; // half of a second, in frames.
-
- // Initialise integer scale bounds.
- short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
- // overridden depending on viewport res
-
- short integerScale_maximumBounds = 1; // can change when window is resized
- // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS.
- if (((float) gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) >
- ((float) gfx_current_dimensions.width / gfx_current_dimensions.height)) {
- // Scale to window height
- integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height;
- } else {
- // Scale to window width
- integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width;
- }
- // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS.
- // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded.
- if (default_maxIntegerScaleFactor < integerScale_maximumBounds) {
- max_integerScaleFactor =
- integerScale_maximumBounds + CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0);
- }
-
- // Stored Values for non-UIWidgets elements
- static float aspectRatioX = CVarGetFloat("gAdvancedResolution.AspectRatioX", 16.0f);
- static float aspectRatioY = CVarGetFloat("gAdvancedResolution.AspectRatioY", 9.0f);
- static int verticalPixelCount = CVarGetInteger("gAdvancedResolution.VerticalPixelCount", 480);
- // Combo List defaults
- static int item_aspectRatio = default_aspectRatio;
- static int item_pixelCount = default_pixelCount;
- // Additional settings
- static bool showHorizontalResField = false;
- static int horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
-
+enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 };
+const ImVec4 messageColor[]{
+ { 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR
+ { 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING
+ { 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION
+ { 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO
+ { 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75
+};
+static const float enhancementSpacerHeight = 19.0f;
+// Initialise update flags.
+static bool update[3];
+
+// Initialise integer scale bounds.
+static short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
+ // overridden depending on viewport res
+
+static short integerScale_maximumBounds = 1; // can change when window is resized
+
+// Combo List defaults
+static int32_t item_aspectRatio;
+static int32_t item_pixelCount;
+// Stored Values for non-UIWidgets elements
+static float aspectRatioX;
+static float aspectRatioY;
+static int32_t verticalPixelCount;
+// Additional settings
+static bool showHorizontalResField;
+static int32_t horizontalPixelCount;
+// Disabling flags
+static bool disabled_everything;
+static bool disabled_pixelCount;
+
+using namespace UIWidgets;
+
+void RegisterResolutionWidgets() {
+ WidgetPath path = { "Settings", "Graphics", SECTION_COLUMN_2 };
#ifdef __APPLE__
- ImGui::Text("Note: these settings may behave incorrectly on Apple Retina Displays.");
- UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+ mPortMenu
+ ->AddWidget(path, ICON_FA_INFO_CIRCLE " These settings may behave incorrectly on Retina displays.", WIDGET_TEXT)
+ .Options(WidgetOptions().Color(Colors::Green));
#endif
- // The original resolution slider (for convenience)
- if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f, 2.0f,
- "", 1.0f, true, true,
- (CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0) &&
- CVarGetInteger("gAdvancedResolution.Enabled", 0)) ||
- CVarGetInteger("gLowResMode", 0))) {
- Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1));
- }
- UIWidgets::Tooltip("Multiplies your output resolution by the value entered, as a more intensive but effective "
- "form of anti-aliasing"); // Description pulled from SohMenuBar.cpp
-
- // N64 Mode toggle (again for convenience)
- // UIWidgets::PaddedEnhancementCheckbox("(Enhancements>Graphics) N64 Mode", "gLowResMode", false, false, false,
- // "", UIWidgets::CheckboxGraphics::Cross, false);
-
- UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
- // Activator
- UIWidgets::PaddedEnhancementCheckbox("Enable advanced settings.", "gAdvancedResolution.Enabled", false, false,
- false, "", UIWidgets::CheckboxGraphics::Cross, false);
- // Error/Warning display
- if (!CVarGetInteger("gLowResMode", 0)) {
- if (IsDroppingFrames()) { // Significant frame drop warning
- ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f },
- ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.");
- UIWidgets::Spacer(2);
- } else { // No warnings
- UIWidgets::Spacer(enhancementSpacerHeight);
- }
- } else { // N64 Mode warning
- ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
- ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.");
- ImGui::SameLine();
- if (ImGui::Button("Click to disable")) {
- CVarSetInteger("gLowResMode", (int) false);
- CVarSave();
- }
- }
- // Resolution visualiser
- ImGui::Text("Viewport dimensions: %d x %d", gfx_current_game_window_viewport.width,
- gfx_current_game_window_viewport.height);
- ImGui::Text("Internal resolution: %d x %d", gfx_current_dimensions.width, gfx_current_dimensions.height);
-
- UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
-
- // Aspect Ratio
- ImGui::Text("Force aspect ratio:");
- ImGui::SameLine();
- ImGui::TextColored({ 0.75f, 0.75f, 0.75f, 1.0f }, "(Select \"Off\" to disable.)");
- if (ImGui::Combo(" ", &item_aspectRatio, aspectRatioPresetLabels,
- IM_ARRAYSIZE(aspectRatioPresetLabels)) &&
- item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected.
- aspectRatioX = aspectRatioPresetsX[item_aspectRatio];
- aspectRatioY = aspectRatioPresetsY[item_aspectRatio];
- update[UPDATE_aspectRatioX] = true;
- update[UPDATE_aspectRatioY] = true;
+ // Resolution visualiser
+ mPortMenu->AddWidget(path, "Viewport dimensions: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
+ info.name = fmt::format("Viewport dimensions: {} x {}", gfx_current_game_window_viewport.width,
+ gfx_current_game_window_viewport.height);
+ });
+ mPortMenu->AddWidget(path, "Internal resolution: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
+ info.name =
+ fmt::format("Internal resolution: {} x {}", gfx_current_dimensions.width, gfx_current_dimensions.height);
+ });
+
+ // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+ // Activator
+ mPortMenu->AddWidget(path, "Enable advanced settings.", WIDGET_CVAR_CHECKBOX)
+ .CVar(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled");
+ /*UIWidgets::PaddedEnhancementCheckbox(, , false, false,
+ false, "", UIWidgets::CheckboxGraphics::Cross, false);*/
+ // Error/Warning display
+ mPortMenu
+ ->AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.",
+ WIDGET_TEXT)
+ .PreFunc(
+ [](WidgetInfo& info) { info.isHidden = !(!CVarGetInteger(CVAR_LOW_RES_MODE, 0) && IsDroppingFrames()); })
+ .Options(WidgetOptions().Color(Colors::Orange));
+ mPortMenu->AddWidget(path, ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.", WIDGET_TEXT)
+ .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); })
+ .Options(WidgetOptions().Color(Colors::LightBlue));
+ mPortMenu->AddWidget(path, "Click to disable N64 mode", WIDGET_BUTTON)
+ .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); })
+ .Callback([](WidgetInfo& info) {
+ CVarSetInteger(CVAR_LOW_RES_MODE, 0);
+ CVarSave();
+ });
+
+ // if (disabled_everything) { // Hide aspect ratio controls.
+ // UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+ // }
+
+ // Aspect Ratio
+ mPortMenu->AddWidget(path, "AspectSep", WIDGET_SEPARATOR);
+ mPortMenu->AddWidget(path, "Force aspect ratio:", WIDGET_TEXT);
+ mPortMenu->AddWidget(path, "(Select \"Off\" to disable.)", WIDGET_TEXT)
+ .SameLine(true)
+ .Options(WidgetOptions().Color(Colors::Gray));
+ // Presets
+ mPortMenu->AddWidget(path, "Aspect Ratio", WIDGET_COMBOBOX)
+ .ValuePointer(&item_aspectRatio)
+ .PreFunc([](WidgetInfo& info) {
+ info.isHidden = !mPortMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active;
+ })
+ .Callback([](WidgetInfo& info) {
+ if (item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected.
+ aspectRatioX = aspectRatioPresetsX[item_aspectRatio];
+ aspectRatioY = aspectRatioPresetsY[item_aspectRatio];
+
+ if (showHorizontalResField) {
+ horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ }
- if (showHorizontalResField) {
- horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
+ CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
}
- }
+ CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
+ CVarSave();
+ })
+ .Options(ComboboxOptions().ComboMap(aspectRatioPresetLabels));
+ mPortMenu->AddWidget(path, "AspectRationCustom", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
+ // Hide aspect ratio input fields if using one of the presets.
if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) {
- // Declaring the Y input interaction in particular as a variable beforehand
- // will prevent a bug where the Y field would disappear when modifying X.
- bool inputX = ImGui::InputFloat("X", &aspectRatioX, 0.1f, 1.0f, "%.3f");
- bool inputY = ImGui::InputFloat("Y", &aspectRatioY, 0.1f, 1.0f, "%.3f");
- if (inputX || inputY) {
+ // Declare input interaction bools outside of IF statement to prevent Y field from disappearing.
+ const bool input_X = ImGui::InputFloat("X", &aspectRatioX, 0.1f, 1.0f, "%.3f");
+ const bool input_Y = ImGui::InputFloat("Y", &aspectRatioY, 0.1f, 1.0f, "%.3f");
+ if (input_X || input_Y) {
item_aspectRatio = default_aspectRatio;
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
}
} else if (showHorizontalResField) { // Show calculated aspect ratio
if (item_aspectRatio) {
- UIWidgets::Spacer(2);
- float resolvedAspectRatio = (float) gfx_current_dimensions.height / gfx_current_dimensions.width;
- ImGui::Text("Aspect ratio: %.4f", resolvedAspectRatio);
- } else {
- UIWidgets::Spacer(enhancementSpacerHeight);
+ ImGui::Dummy({ 0, 2 });
+ const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height;
+ ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio);
}
}
+ });
+ //
+ // if (disabled_everything) { // Hide aspect ratio controls.
+ // UIWidgets::ReEnableComponent("disabledTooltipText");
+ // }
+ // UIWidgets::Spacer(0);
+ //
+ // // Vertical Resolution
+ // UIWidgets::PaddedEnhancementCheckbox("Set fixed vertical resolution (disables Resolution slider)",
+ // CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", true,
+ // false, disabled_everything, "", UIWidgets::CheckboxGraphics::Cross,
+ // false);
+ // UIWidgets::Tooltip(
+ // "Override the resolution scale slider and use the settings below, irrespective of window size.");
+ // if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls.
+ // UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+ // }
+ // if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels,
+ // IM_ARRAYSIZE(pixelCountPresetLabels)) &&
+ // item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected.
+ // verticalPixelCount = pixelCountPresets[item_pixelCount];
+ //
+ // if (showHorizontalResField) {
+ // horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ // }
+ //
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount);
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount);
+ // CVarSave();
+ // }
+ // // Horizontal Resolution, if visibility is enabled for it.
+ // if (showHorizontalResField) {
+ // // Only show the field if Aspect Ratio is being enforced.
+ // if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) {
+ // // So basically we're "faking" this one by setting aspectRatioX instead.
+ // if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) {
+ // item_aspectRatio = default_aspectRatio;
+ // if (horizontalPixelCount < SCREEN_WIDTH) {
+ // horizontalPixelCount = SCREEN_WIDTH;
+ // }
+ // aspectRatioX = horizontalPixelCount;
+ // aspectRatioY = verticalPixelCount;
+ // update[UPDATE_aspectRatioX] = true;
+ // update[UPDATE_aspectRatioY] = true;
+ // }
+ // } else { // Display a notice instead.
+ // ImGui::TextColored(messageColor[MESSAGE_QUESTION],
+ // ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required.");
+ // // ImGui::Text(" ");
+ // ImGui::SameLine();
+ // if (ImGui::Button("Click to resolve")) {
+ // item_aspectRatio = default_aspectRatio; // Set it to Custom
+ // aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults
+ // aspectRatioY = aspectRatioPresetsY[2];
+ // update[UPDATE_aspectRatioX] = true;
+ // update[UPDATE_aspectRatioY] = true;
+ // horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ // }
+ // }
+ // }
+ // // Vertical Resolution part 2
+ // if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) {
+ // item_pixelCount = default_pixelCount;
+ // update[UPDATE_verticalPixelCount] = true;
+ //
+ // // Account for the natural instinct to enter horizontal first.
+ // // Ignore vertical resolutions that are below the lower clamp constant.
+ // if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) {
+ // item_aspectRatio = default_aspectRatio;
+ // aspectRatioX = horizontalPixelCount;
+ // aspectRatioY = verticalPixelCount;
+ // update[UPDATE_aspectRatioX] = true;
+ // update[UPDATE_aspectRatioY] = true;
+ // }
+ // }
+ // if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls.
+ // UIWidgets::ReEnableComponent("disabledTooltipText");
+ // }
+ //
+ // UIWidgets::Spacer(0);
+ //
+ // // Integer scaling settings group (Pixel-perfect Mode)
+ // static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag =
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen
+ // : ImGuiTreeNodeFlags_None;
+ // if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) {
+ // const bool disabled_pixelPerfectMode =
+ // !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything;
+ // // Pixel-perfect Mode
+ // UIWidgets::PaddedEnhancementCheckbox("Pixel-perfect Mode", CVAR_PREFIX_ADVANCED_RESOLUTION
+ // ".PixelPerfectMode", true,
+ // true, disabled_pixelCount || disabled_everything, "",
+ // UIWidgets::CheckboxGraphics::Cross, false);
+ // UIWidgets::Tooltip("Don't scale image to fill window.");
+ // if (disabled_pixelCount && CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0)) {
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0);
+ // CVarSave();
+ // }
+ //
+ // // Integer Scaling
+ // UIWidgets::EnhancementSliderInt(
+ // "Integer scale factor: %d", "##ARSIntScale", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor",
+ // 1, max_integerScaleFactor, "%d", 1, true, disabled_pixelPerfectMode ||
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0));
+ // UIWidgets::Tooltip("Integer scales the image. Only available in pixel-perfect mode.");
+ // // Display warning if size is being clamped or if framebuffer is larger than viewport.
+ // if (!disabled_pixelPerfectMode &&
+ // (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) &&
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1) >
+ // integerScale_maximumBounds)) {
+ // ImGui::SameLine();
+ // ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded.");
+ // }
+ //
+ // UIWidgets::PaddedEnhancementCheckbox(
+ // "Automatically scale image to fit viewport", CVAR_PREFIX_ADVANCED_RESOLUTION
+ // ".IntegerScale.FitAutomatically", true, true, disabled_pixelPerfectMode, "",
+ // UIWidgets::CheckboxGraphics::Cross, false);
+ // UIWidgets::Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect
+ // mode."); if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)) {
+ // // This is just here to update the value shown on the slider.
+ // // The function in LUS to handle this setting will ignore IntegerScaleFactor while active.
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", integerScale_maximumBounds);
+ // // CVarSave();
+ // }
+ // } // End of integer scaling settings
+ //
+ // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+ //
+ // // Collapsible panel for additional settings
+ // if (ImGui::CollapsingHeader("Additional Settings")) {
+ // UIWidgets::Spacer(0);
+ //
+ //#if defined(__SWITCH__) || defined(__WIIU__)
+ // // Disable aspect correction, stretching the framebuffer to fill the viewport.
+ // // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles.
+ // // The associated cvar is still functional on PC platforms if you want to use it though.
+ // UIWidgets::PaddedEnhancementCheckbox("Disable aspect correction and stretch the output image.\n"
+ // "(Might be useful for 4:3 televisions!)\n"
+ // "Not available in Pixel Perfect Mode.",
+ // CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", false,
+ // true, CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION
+ // ".PixelPerfectMode", 0) ||
+ // disabled_everything,
+ // "", UIWidgets::CheckboxGraphics::Cross, false);
+ //#else
+ // if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0)) {
+ // // This setting is intentionally not exposed on PC platforms,
+ // // but may be accidentally activated for varying reasons.
+ // // Having this button should hopefully prevent support headaches.
+ // ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE
+ // " If the image is stretched and you don't know why, click this.");
+ // if (ImGui::Button("Click to reenable aspect correction.")) {
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0);
+ // CVarSave();
+ // }
+ // UIWidgets::Spacer(2);
+ // }
+ //#endif
+ //
+ // // A requested addition; an alternative way of displaying the resolution field.
+ // if (ImGui::Checkbox("Show a horizontal resolution field, instead of aspect ratio.",
+ // &showHorizontalResField)) {
+ // if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off
+ // // Refresh relevant values
+ // aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
+ // horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ // } else { // when turning this setting on
+ // item_aspectRatio = default_aspectRatio;
+ // if (aspectRatioX > 0.0f) {
+ // // Refresh relevant values in the opposite order
+ // horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ // aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
+ // }
+ // }
+ // update[UPDATE_aspectRatioX] = true;
+ // }
+ //
+ // // Beginning of Integer Scaling additional settings.
+ // {
+ // // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+ //
+ // // Integer Scaling - Never Exceed Bounds.
+ // const bool disabled_neverExceedBounds =
+ // !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ||
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0) ||
+ // disabled_everything;
+ // const bool checkbox_neverExceedBounds =
+ // UIWidgets::PaddedEnhancementCheckbox("Prevent integer scaling from exceeding screen bounds.\n"
+ // "(Makes screen bounds take priority over specified
+ // factor.)", CVAR_PREFIX_ADVANCED_RESOLUTION
+ // ".IntegerScale.NeverExceedBounds", true, false,
+ // disabled_neverExceedBounds, "",
+ // UIWidgets::CheckboxGraphics::Cross, true);
+ // UIWidgets::Tooltip(
+ // "Prevents integer scaling factor from exceeding screen bounds.\n\n"
+ // "Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n"
+ // "Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n"
+ // " " ICON_FA_INFO_CIRCLE
+ // " Please note that exceeding screen bounds may show a scroll bar on-screen.");
+ //
+ // // Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed.
+ // if (checkbox_neverExceedBounds &&
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) {
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0);
+ // CVarSave();
+ // }
+ //
+ // // Integer Scaling - Exceed Bounds By 1x/Offset.
+ // // A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p
+ // 5x".
+ // /*
+ // UIWidgets::PaddedEnhancementCheckbox("Allow integer scale factor to go +1 above maximum screen
+ // bounds.", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", false, false,
+ // !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything, "",
+ // UIWidgets::CheckboxGraphics::Cross, false);
+ // */
+ // // It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar.
+ // // I've ended up commenting this one out because of the scroll bar, and for simplicity.
+ //
+ // // Display an info message about the scroll bar.
+ // if (!CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) ||
+ // CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) {
+ // if (disabled_neverExceedBounds) { // Dim this help text accordingly
+ // UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
+ // }
+ // ImGui::TextColored(messageColor[MESSAGE_INFO],
+ // " " ICON_FA_INFO_CIRCLE
+ // " A scroll bar may become visible if screen bounds are exceeded.");
+ // if (disabled_neverExceedBounds) { // Dim this help text accordingly
+ // UIWidgets::ReEnableComponent("disabledTooltipText");
+ // }
+ //
+ // // Another support helper button, to disable the unused "Exceed Bounds By" cvar.
+ // // (Remove this button if uncommenting the checkbox.)
+ // if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) {
+ // if (ImGui::Button("Click to reset a console variable that may be causing this.")) {
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0);
+ // CVarSave();
+ // }
+ // }
+ // } else {
+ // ImGui::Text(" ");
+ // }
+ // // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
+ // } // End of Integer Scaling additional settings.
+ //
+ // } // End of additional settings
+ //
+ // // Clamp and update the cvars that don't use UIWidgets
+ // if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) {
+ // if (update[UPDATE_aspectRatioX]) {
+ // if (aspectRatioX < 0.0f) {
+ // aspectRatioX = 0.0f;
+ // }
+ // CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
+ // }
+ // if (update[UPDATE_aspectRatioY]) {
+ // if (aspectRatioY < 0.0f) {
+ // aspectRatioY = 0.0f;
+ // }
+ // CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
+ // }
+ // if (update[UPDATE_verticalPixelCount]) {
+ // // There's a upper and lower clamp on the Libultraship side too,
+ // // so clamping it here is entirely visual, so the vertical resolution field reflects it.
+ // if (verticalPixelCount < minVerticalPixelCount) {
+ // verticalPixelCount = minVerticalPixelCount;
+ // }
+ // if (verticalPixelCount > maxVerticalPixelCount) {
+ // verticalPixelCount = maxVerticalPixelCount;
+ // }
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount);
+ // }
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
+ // CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount);
+ // CVarSave();
+ // }
+}
- UIWidgets::Spacer(0);
- // Vertical Resolution
- UIWidgets::PaddedEnhancementCheckbox("Set fixed vertical resolution (disables Resolution slider)",
- "gAdvancedResolution.VerticalResolutionToggle", true, false, false, "",
- UIWidgets::CheckboxGraphics::Cross, false);
- UIWidgets::Tooltip(
- "Override the resolution scale slider and use the settings below, irrespective of window size.");
- if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels,
- IM_ARRAYSIZE(pixelCountPresetLabels)) &&
- item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected.
- verticalPixelCount = pixelCountPresets[item_pixelCount];
- update[UPDATE_verticalPixelCount] = true;
-
- if (showHorizontalResField) {
- horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+void UpdateResolutionVars() {
+ // Clamp and update the cvars that don't use UIWidgets
+ if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) {
+ if (update[UPDATE_aspectRatioX]) {
+ if (aspectRatioX < 0.0f) {
+ aspectRatioX = 0.0f;
}
+ CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
}
- // Horizontal Resolution, if visibility is enabled for it.
- if (showHorizontalResField) {
- // Only show the field if Aspect Ratio is being enforced.
- if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) {
- // So basically we're "faking" this one by setting aspectRatioX instead.
- if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) {
- item_aspectRatio = default_aspectRatio;
- if (horizontalPixelCount < (minVerticalPixelCount / 3.0f) * 4.0f) {
- horizontalPixelCount = (minVerticalPixelCount / 3.0f) * 4.0f;
- }
- aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
- update[UPDATE_aspectRatioX] = true;
- }
- } else { // Display a notice instead.
- ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
- ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required.");
- // ImGui::Text(" ");
- ImGui::SameLine();
- if (ImGui::Button("Click to resolve")) {
- item_aspectRatio = default_aspectRatio; // Set it to Custom
- aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults
- aspectRatioY = aspectRatioPresetsY[2];
- update[UPDATE_aspectRatioX] = true;
- update[UPDATE_aspectRatioY] = true;
- horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
- }
- }
- }
- // Vertical Resolution part 2
- if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) {
- item_pixelCount = default_pixelCount;
- update[UPDATE_verticalPixelCount] = true;
-
- // Account for the natural instinct to enter horizontal first.
- // Ignore vertical resolutions that are below the lower clamp constant.
- if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) {
- aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
- update[UPDATE_aspectRatioX] = true;
+ if (update[UPDATE_aspectRatioY]) {
+ if (aspectRatioY < 0.0f) {
+ aspectRatioY = 0.0f;
}
+ CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
}
-
- UIWidgets::Spacer(0);
- // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
-
- // Integer scaling settings group
- if (ImGui::CollapsingHeader("Integer Scaling Settings")) {
- // Pixel-perfect Mode
- UIWidgets::PaddedEnhancementCheckbox("Pixel-perfect Mode", "gAdvancedResolution.PixelPerfectMode", true,
- true,
- !CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0), "",
- UIWidgets::CheckboxGraphics::Cross, false);
- UIWidgets::Tooltip("Don't scale image to fill window.");
- if (!CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0)) {
- CVarSetInteger("gAdvancedResolution.PixelPerfectMode", (int) false);
- CVarSave();
+ if (update[UPDATE_verticalPixelCount]) {
+ // There's a upper and lower clamp on the Libultraship side too,
+ // so clamping it here is entirely visual, so the vertical resolution field reflects it.
+ if (verticalPixelCount < minVerticalPixelCount) {
+ verticalPixelCount = minVerticalPixelCount;
}
-
- // Integer Scaling
- UIWidgets::EnhancementSliderInt("Integer scale factor: %d", "##ARSIntScale",
- "gAdvancedResolution.IntegerScale.Factor", 1, max_integerScaleFactor, "%d",
- 1, true,
- !CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) ||
- CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0));
- UIWidgets::Tooltip("Integer scales the image. Only available in pixel-perfect mode.");
- // Display warning if size is being clamped or if framebuffer is larger than viewport.
- if (CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) &&
- (CVarGetInteger("gAdvancedResolution.IntegerScale.NeverExceedBounds", 1) &&
- CVarGetInteger("gAdvancedResolution.IntegerScale.Factor", 1) > integerScale_maximumBounds)) {
- ImGui::SameLine();
- ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f }, ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded.");
- }
-
- UIWidgets::PaddedEnhancementCheckbox("Automatically scale image to fit viewport",
- "gAdvancedResolution.IntegerScale.FitAutomatically", true, true,
- !CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0), "",
- UIWidgets::CheckboxGraphics::Cross, false);
- UIWidgets::Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode.");
- if (CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0)) {
- // This is just here to update the value shown on the slider.
- // The function in LUS to handle this setting will ignore IntegerScaleFactor while active.
- CVarSetInteger("gAdvancedResolution.IntegerScale.Factor", integerScale_maximumBounds);
- // CVarSave();
+ if (verticalPixelCount > maxVerticalPixelCount) {
+ verticalPixelCount = maxVerticalPixelCount;
}
+ CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount);
}
+ CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
+ CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount);
+ CVarSave();
+ }
+ // Initialise update flags.
+ for (uint8_t i = 0; i < sizeof(update); i++) {
+ update[i] = false;
+ }
- UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
-
- // Collapsible panel for additional settings
- if (ImGui::CollapsingHeader("Additional Settings")) {
- UIWidgets::Spacer(0);
-#if defined(__SWITCH__) || defined(__WIIU__)
- // Disable aspect correction, stretching the framebuffer to fill the viewport.
- // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles.
- // The associated CVar is still functional on PC platforms if you want to use it though.
- UIWidgets::PaddedEnhancementCheckbox("Disable aspect correction and stretch the output image.\n"
- "(Might be useful for 4:3 televisions!)\n"
- "Not available in Pixel Perfect Mode.",
- "gAdvancedResolution.IgnoreAspectCorrection", false, true,
- CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0), "",
- UIWidgets::CheckboxGraphics::Cross, false);
-#else
- if (CVarGetInteger("gAdvancedResolution.IgnoreAspectCorrection", 0)) {
- // This setting is intentionally not exposed on PC platforms,
- // but may be accidentally activated for varying reasons.
- // Having this button should hopefully prevent support headaches.
- ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f }, ICON_FA_QUESTION_CIRCLE
- " If the image is stretched and you don't know why, click this.");
- if (ImGui::Button("Click to reenable aspect correction.")) {
- CVarSetInteger("gAdvancedResolution.IgnoreAspectCorrection", (int) false);
- CVarSave();
- }
- UIWidgets::Spacer(2);
- }
-#endif
-
- if (ImGui::Checkbox("Show a horizontal resolution field.", &showHorizontalResField)) {
- if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off
- // Refresh relevant values
- aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
- horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
- } else { // when turning this setting on
- item_aspectRatio = default_aspectRatio;
- if (aspectRatioX > 0.0f) {
- // Refresh relevant values in the opposite order
- horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
- aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
- }
- }
- update[UPDATE_aspectRatioX] = true;
- }
-
- UIWidgets::PaddedEnhancementCheckbox(
- "Don't allow integer scaling to exceed screen bounds.\n"
- "(Makes screen bounds take priority over specified factor.)",
- "gAdvancedResolution.IntegerScale.NeverExceedBounds", true, false,
- !CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) ||
- CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0),
- "", UIWidgets::CheckboxGraphics::Cross, true);
-
- if (!CVarGetInteger("gAdvancedResolution.IntegerScale.NeverExceedBounds", 1) ||
- CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
- ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
- " " ICON_FA_QUESTION_CIRCLE
- " A scroll bar may become visible if screen bounds are exceeded.");
- // Another helpful button for an unused CVar.
- if (CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
- if (ImGui::Button("Click to reset an unused CVar that may be causing this.")) {
- CVarSetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0);
- CVarSave();
- }
- }
- } else {
- UIWidgets::Spacer(enhancementSpacerHeight);
- }
-
- // I've ended up dummying this one out because it doesn't function in a satisfactory way.
- // Consider this idea on the table, but I don't deem it an important enough feature to push for.
- /*
- UIWidgets::PaddedEnhancementCheckbox("Allow integer scale factor to go 1x above maximum screen bounds.",
- "gAdvancedResolution.IntegerScale.ExceedBoundsBy", false, false,
- !CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0), "",
- UIWidgets::CheckboxGraphics::Cross, false);
- if (CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
- ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
- " " ICON_FA_QUESTION_CIRCLE
- " A scroll bar may become visible if screen bounds are exceeded.");
- }*/
-
- } // end of Additional Settings
-
- // Clamp and update the CVars that don't use UIWidgets
- if (IsBoolArrayTrue(update)) {
- if (update[UPDATE_aspectRatioX]) {
- if (aspectRatioX < 0.0f) {
- aspectRatioX = 0.0f;
- }
- CVarSetFloat("gAdvancedResolution.AspectRatioX", aspectRatioX);
- }
- if (update[UPDATE_aspectRatioY]) {
- if (aspectRatioY < 0.0f) {
- aspectRatioY = 0.0f;
- }
- CVarSetFloat("gAdvancedResolution.AspectRatioY", aspectRatioY);
- }
- if (update[UPDATE_verticalPixelCount]) {
- // There's a upper and lower clamp on the Libultraship side too,
- // so clamping it here is purely visual, so the vertical resolution field reflects it.
- if (verticalPixelCount < minVerticalPixelCount) {
- verticalPixelCount = minVerticalPixelCount;
- }
- if (verticalPixelCount > maxVerticalPixelCount) {
- verticalPixelCount = maxVerticalPixelCount;
- }
- CVarSetInteger("gAdvancedResolution.VerticalPixelCount", verticalPixelCount);
- }
- // Delay saving this set of CVars by a predetermined length of time, in frames.
- updateCountdown = countdownStartingValue;
- }
- if (updateCountdown > 0) {
- updateCountdown--;
- } else {
- CVarSave();
- }
+ // Initialise integer scale bounds.
+ short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
+ // overridden depending on viewport res
+
+ short integerScale_maximumBounds = 1; // can change when window is resized
+ // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS.
+ if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) >
+ ((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) {
+ // Scale to window height
+ integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height;
+ } else {
+ // Scale to window width
+ integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width;
+ }
+ // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS.
+ // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded.
+ if (default_maxIntegerScaleFactor < integerScale_maximumBounds) {
+ max_integerScaleFactor = integerScale_maximumBounds +
+ CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0);
}
- ImGui::End();
-}
-void AdvancedResolutionSettingsWindow::UpdateElement() {
+ // Combo List defaults
+ item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3);
+ item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount);
+ // Stored Values for non-UIWidgets elements
+ aspectRatioX = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]);
+ aspectRatioY = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]);
+ verticalPixelCount =
+ CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]);
+ // Additional settings
+ showHorizontalResField = false;
+ horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
+ // Disabling flags
+ disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0);
+ disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0);
}
-bool AdvancedResolutionSettingsWindow::IsDroppingFrames() {
+bool IsDroppingFrames() {
// a rather imprecise way of checking for frame drops.
// but it's mostly there to inform the player of large drops.
- const short targetFPS = CVarGetInteger("gInterpolationFPS", 30);
+ const short targetFPS = CVarGetInteger("gInterpolationFPS", 20);
const float threshold = targetFPS / 20.0f + 4.1f;
return ImGui::GetIO().Framerate < targetFPS - threshold;
}
-bool AdvancedResolutionSettingsWindow::IsBoolArrayTrue(bool* foo) {
- for (unsigned short i = 0; i < sizeof(&foo); i++)
- if (&foo[i])
- return true;
- return false;
-}
-} // namespace AdvancedResolutionSettings
+static RegisterMenuUpdateFunc updateFunc(UpdateResolutionVars, "Settings", "Graphics");
+static RegisterMenuInitFunc initFunc(RegisterResolutionWidgets);
+
+} // namespace BenGui
diff --git a/src/port/ui/ResolutionEditor.h b/src/port/ui/ResolutionEditor.h
index 8894422c1..a287bb282 100644
--- a/src/port/ui/ResolutionEditor.h
+++ b/src/port/ui/ResolutionEditor.h
@@ -1,19 +1,12 @@
-#pragma once
-#include <libultraship/libultraship.h>
-
-namespace AdvancedResolutionSettings {
-class AdvancedResolutionSettingsWindow : public Ship::GuiWindow {
- private:
- bool IsDroppingFrames();
+#ifndef RESOLUTIONEDITOR_H
+#define RESOLUTIONEDITOR_H
- protected:
- bool IsBoolArrayTrue(bool*);
+#include <libultraship/libultraship.h>
- public:
- using Ship::GuiWindow::GuiWindow;
+namespace GameUI {
+bool IsDroppingFrames();
+void RegisterResolutionWidgets();
+void UpdateResolutionVars();
+} // namespace BenGui
- void InitElement() override;
- void DrawElement() override;
- void UpdateElement() override;
-};
-} // namespace AdvancedResolutionSettings
+#endif // RESOLUTIONEDITOR_H
diff --git a/src/port/ui/UIWidgets.cpp b/src/port/ui/UIWidgets.cpp
index b97256765..1368cf420 100644
--- a/src/port/ui/UIWidgets.cpp
+++ b/src/port/ui/UIWidgets.cpp
@@ -1,27 +1,19 @@
-//
-// UIWidgets.cpp
-// soh
-//
-// Created by David Chavez on 25.08.22.
-//
-
#include "UIWidgets.h"
-
-#include "libultraship/src/Context.h"
-
-#include <imgui.h>
+#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
+#include <sstream>
#include <libultraship/libultraship.h>
+#include <string>
+#include <unordered_map>
#include <libultraship/libultra/types.h>
+#include <spdlog/fmt/fmt.h>
namespace UIWidgets {
-// MARK: - Layout Helper
-
// Automatically adds newlines to break up text longer than a specified number of characters
// Manually included newlines will still be respected and reset the line length
// If line is midword when it hits the limit, text should break at the last encountered space
-char* WrappedText(const char* text, unsigned int charactersPerLine) {
+std::string WrappedText(const char* text, unsigned int charactersPerLine) {
std::string newText(text);
const size_t tipLength = newText.length();
int lastSpace = -1;
@@ -43,615 +35,41 @@ char* WrappedText(const char* text, unsigned int charactersPerLine) {
currentLineLength++;
}
- return strdup(newText.c_str());
+ return newText;
}
-char* WrappedText(const std::string& text, unsigned int charactersPerLine) {
+std::string WrappedText(const std::string& text, unsigned int charactersPerLine) {
return WrappedText(text.c_str(), charactersPerLine);
}
-void SetLastItemHoverText(const std::string& text) {
- if (ImGui::IsItemHovered()) {
- ImGui::BeginTooltip();
- ImGui::Text("%s", WrappedText(text, 60));
- ImGui::EndTooltip();
- }
-}
-
-void SetLastItemHoverText(const char* text) {
- if (ImGui::IsItemHovered()) {
- ImGui::BeginTooltip();
- ImGui::Text("%s", WrappedText(text, 60));
- ImGui::EndTooltip();
- }
-}
-
-// Adds a "?" next to the previous ImGui item with a custom tooltip
-void InsertHelpHoverText(const std::string& text) {
- ImGui::SameLine();
- ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?");
- if (ImGui::IsItemHovered()) {
- ImGui::BeginTooltip();
- ImGui::Text("%s", WrappedText(text, 60));
- ImGui::EndTooltip();
- }
-}
-
-void InsertHelpHoverText(const char* text) {
- ImGui::SameLine();
- ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?");
- if (ImGui::IsItemHovered()) {
- ImGui::BeginTooltip();
- ImGui::Text("%s", WrappedText(text, 60));
- ImGui::EndTooltip();
- }
-}
-
-// MARK: - UI Elements
-
void Tooltip(const char* text) {
if (ImGui::IsItemHovered()) {
- ImGui::SetTooltip("%s", WrappedText(text));
- }
-}
-
-void Spacer(float height) {
- ImGui::Dummy(ImVec2(0.0f, height));
-}
-
-void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) {
- if (padTop) {
- Spacer(extraVerticalTopPadding);
- }
- ImGui::Separator();
- if (padBottom) {
- Spacer(extraVerticalBottomPadding);
- }
-}
-
-void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) {
- float thickness = ImMax(sz / 5.0f, 1.0f);
- sz -= thickness * 0.5f;
- pos += ImVec2(thickness * 0.25f, thickness * 0.25f);
-
- draw_list->PathLineTo(ImVec2(pos.x, pos.y));
- draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y + sz));
- draw_list->PathStroke(col, 0, thickness);
-
- draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y));
- draw_list->PathLineTo(ImVec2(pos.x, pos.y + sz));
- draw_list->PathStroke(col, 0, thickness);
-}
-
-bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic) {
- ImGuiWindow* window = ImGui::GetCurrentWindow();
- if (window->SkipItems) {
- return false;
- }
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
-
- const float square_sz = ImGui::GetFrameHeight();
- const ImVec2 pos = window->DC.CursorPos;
- const ImRect total_bb(
- pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f),
- label_size.y + style.FramePadding.y * 2.0f));
- ImGui::ItemSize(total_bb, style.FramePadding.y);
- if (!ImGui::ItemAdd(total_bb, id)) {
- IMGUI_TEST_ENGINE_ITEM_INFO(id, label,
- g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable |
- (*v ? ImGuiItemStatusFlags_Checked : 0));
- return false;
- }
-
- bool hovered, held;
- bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held);
- if (pressed) {
- *v = !(*v);
- ImGui::MarkItemEdited(id);
- }
-
- const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
- ImGui::RenderNavHighlight(total_bb, id);
- ImGui::RenderFrame(check_bb.Min, check_bb.Max,
- ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive
- : hovered ? ImGuiCol_FrameBgHovered
- : ImGuiCol_FrameBg),
- true, style.FrameRounding);
- ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark);
- ImU32 cross_col = ImGui::GetColorU32(ImVec4(0.50f, 0.50f, 0.50f, 1.00f));
- bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
- if (mixed_value) {
- // Undocumented tristate/mixed/indeterminate checkbox (#2644)
- // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all
- // widgets (not just checkbox)
- ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
- window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
- } else if ((!disabled && *v) || (disabled && disabledGraphic == CheckboxGraphics::Checkmark)) {
- const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
- ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
- } else if (disabled && disabledGraphic == CheckboxGraphics::Cross) {
- const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
- RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), cross_col, square_sz - pad * 2.0f);
- }
-
- ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
- if (g.LogEnabled) {
- ImGui::LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
- }
- if (label_size.x > 0.0f) {
- ImGui::RenderText(label_pos, label);
- }
-
- IMGUI_TEST_ENGINE_ITEM_INFO(id, label,
- g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable |
- (*v ? ImGuiItemStatusFlags_Checked : 0));
- return pressed;
-}
-
-void ReEnableComponent(const char* disabledTooltipText) {
- // End of disable region of previous component
- ImGui::PopStyleVar(1);
- if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(disabledTooltipText, "") != 0) {
- ImGui::SetTooltip("%s", disabledTooltipText);
- }
- ImGui::PopItemFlag();
-}
-
-void DisableComponent(const float alpha) {
- ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
- ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
-}
-
-bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText,
- CheckboxGraphics disabledGraphic, bool defaultValue) {
- bool changed = false;
- if (disabled) {
- DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
-
- bool val = (bool) CVarGetInteger(cvarName, defaultValue);
- if (CustomCheckbox(text, &val, disabled, disabledGraphic)) {
- CVarSetInteger(cvarName, val);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- changed = true;
- }
-
- if (disabled) {
- ReEnableComponent(disabledTooltipText);
- }
- return changed;
-}
-
-bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop, bool padBottom, bool disabled,
- const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) {
- ImGui::BeginGroup();
- if (padTop)
- Spacer(0);
-
- bool changed = EnhancementCheckbox(text, cvarName, disabled, disabledTooltipText, disabledGraphic, defaultValue);
-
- if (padBottom)
- Spacer(0);
- ImGui::EndGroup();
- return changed;
-}
-
-bool EnhancementCombobox(const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray,
- uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) {
- bool changed = false;
- if (defaultIndex <= 0) {
- defaultIndex = 0;
- }
-
- if (disabled) {
- DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
-
- uint8_t selected = CVarGetInteger(cvarName, defaultIndex);
- std::string comboName = std::string("##") + std::string(cvarName);
- if (ImGui::BeginCombo(comboName.c_str(), comboArray[selected])) {
- for (uint8_t i = 0; i < comboArray.size(); i++) {
- if (strlen(comboArray[i]) > 1) {
- if (ImGui::Selectable(comboArray[i], i == selected)) {
- CVarSetInteger(cvarName, i);
- selected = i;
- changed = true;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- }
- }
- }
- ImGui::EndCombo();
- }
-
- if (disabled) {
- ReEnableComponent(disabledTooltipText);
-
- if (disabledValue >= 0 && selected != disabledValue) {
- CVarSetInteger(cvarName, disabledValue);
- changed = true;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- }
- }
-
- return changed;
-}
-
-bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName,
- std::span<const char*, std::dynamic_extent> comboArray,
- uint8_t defaultIndex, bool disabled, const char* disabledTooltipText,
- uint8_t disabledValue) {
- ImGui::Text(label);
- s32 currentValue = CVarGetInteger(cvarName, defaultIndex);
-
-#ifdef __WIIU__
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 40.0f));
- ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 60.0f);
-#else
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 20.0f));
- ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 30.0f);
-#endif
-
- bool changed =
- EnhancementCombobox(cvarName, comboArray, defaultIndex, disabled, disabledTooltipText, disabledValue);
-
- ImGui::PopItemWidth();
- return changed;
-}
-
-void PaddedText(const char* text, bool padTop, bool padBottom) {
- if (padTop)
- Spacer(0);
-
- ImGui::Text("%s", text);
-
- if (padBottom)
- Spacer(0);
-}
-
-bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format,
- int defaultValue, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) {
- bool changed = false;
- int val = CVarGetInteger(cvarName, defaultValue);
-
- if (disabled) {
- DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
-
- ImGui::Text(text, val);
- Spacer(0);
-
- ImGui::BeginGroup();
- if (PlusMinusButton) {
- std::string MinusBTNName = " - ##" + std::string(cvarName);
- if (ImGui::Button(MinusBTNName.c_str())) {
- val--;
- changed = true;
- }
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
- }
-
- ImGui::PushItemWidth(
- std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth));
- if (ImGui::SliderInt(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) {
- changed = true;
- }
- ImGui::PopItemWidth();
-
- if (PlusMinusButton) {
- std::string PlusBTNName = " + ##" + std::string(cvarName);
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
- if (ImGui::Button(PlusBTNName.c_str())) {
- val++;
- changed = true;
- }
- }
- ImGui::EndGroup();
-
- if (disabled) {
- ReEnableComponent(disabledTooltipText);
- }
-
- if (val < min) {
- val = min;
- changed = true;
- }
-
- if (val > max) {
- val = max;
- changed = true;
- }
-
- if (changed) {
- CVarSetInteger(cvarName, val);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- }
-
- return changed;
-}
-
-bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
- const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton,
- bool disabled, const char* disabledTooltipText) {
- bool changed = false;
- float val = CVarGetFloat(cvarName, defaultValue);
-
- if (disabled) {
- DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
- }
-
- if (!isPercentage) {
- ImGui::Text(text, val);
- } else {
- ImGui::Text(text, static_cast<int>(100 * val));
- }
- Spacer(0);
-
- ImGui::BeginGroup();
- if (PlusMinusButton) {
- std::string MinusBTNName = " - ##" + std::string(cvarName);
- if (ImGui::Button(MinusBTNName.c_str())) {
- if (isPercentage) {
- val -= 0.01f;
- } else {
- val -= 0.1f;
- }
- changed = true;
- }
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
- }
-
- ImGui::PushItemWidth(
- std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth));
- if (ImGui::SliderFloat(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) {
- if (isPercentage) {
- val = roundf(val * 100) / 100;
- }
- changed = true;
- }
- ImGui::PopItemWidth();
-
- if (PlusMinusButton) {
- std::string PlusBTNName = " + ##" + std::string(cvarName);
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
- if (ImGui::Button(PlusBTNName.c_str())) {
- if (isPercentage) {
- val += 0.01f;
- } else {
- val += 0.1f;
- }
- changed = true;
- }
- }
- ImGui::EndGroup();
-
- if (disabled) {
- ReEnableComponent(disabledTooltipText);
- }
-
- if (val < min) {
- val = min;
- changed = true;
- }
-
- if (val > max) {
- val = max;
- changed = true;
- }
-
- if (changed) {
- CVarSetFloat(cvarName, val);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- }
-
- return changed;
-}
-
-bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max,
- const char* format, int defaultValue, bool PlusMinusButton, bool padTop, bool padBottom,
- bool disabled, const char* disabledTooltipText) {
- bool changed = false;
- ImGui::BeginGroup();
- if (padTop)
- Spacer(0);
-
- changed = EnhancementSliderInt(text, id, cvarName, min, max, format, defaultValue, PlusMinusButton, disabled,
- disabledTooltipText);
-
- if (padBottom)
- Spacer(0);
- ImGui::EndGroup();
- return changed;
-}
-
-bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
- const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton,
- bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) {
- bool changed = false;
- ImGui::BeginGroup();
- if (padTop)
- Spacer(0);
-
- changed = EnhancementSliderFloat(text, id, cvarName, min, max, format, defaultValue, isPercentage, PlusMinusButton,
- disabled, disabledTooltipText);
-
- if (padBottom)
- Spacer(0);
- ImGui::EndGroup();
- return changed;
-}
-
-bool EnhancementRadioButton(const char* text, const char* cvarName, int id) {
- /*Usage :
- EnhancementRadioButton("My Visible Name","gMyCVarName", MyID);
- First arg is the visible name of the Radio button
- Second is the cvar name where MyID will be saved.
- Note: the CVar name should be the same to each Buddies.
- Example :
- EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG);
- EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER);
- EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA);
- */
- std::string make_invisible = "##" + std::string(text) + std::string(cvarName);
-
- bool ret = false;
- int val = CVarGetInteger(cvarName, 0);
- if (ImGui::RadioButton(make_invisible.c_str(), id == val)) {
- CVarSetInteger(cvarName, id);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- ret = true;
- }
- ImGui::SameLine();
- ImGui::Text("%s", text);
-
- return ret;
-}
-
-bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) {
- bool changed = false;
- std::string Cvar_RBM = std::string(cvarName) + "RBM";
- std::string MakeInvisible = "Reset##" + std::string(cvarName) + "Reset";
- if (ImGui::Button(MakeInvisible.c_str())) {
- colors->x = defaultcolors.x;
- colors->y = defaultcolors.y;
- colors->z = defaultcolors.z;
- colors->w = has_alpha ? defaultcolors.w : 255.0f;
-
- Color_RGBA8 colorsRGBA;
- colorsRGBA.r = defaultcolors.x;
- colorsRGBA.g = defaultcolors.y;
- colorsRGBA.b = defaultcolors.z;
- colorsRGBA.a = has_alpha ? defaultcolors.w : 255.0f;
-
- CVarSetColor(cvarName, colorsRGBA);
- CVarSetInteger(Cvar_RBM.c_str(), 0); // On click disable rainbow mode.
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- changed = true;
- }
- Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color");
- return changed;
-}
-
-void DrawLockColorCheckbox(const char* cvarName) {
- std::string Cvar_Lock = std::string(cvarName) + "Lock";
- s32 lock = CVarGetInteger(Cvar_Lock.c_str(), 0);
- std::string FullName = "Lock##" + Cvar_Lock;
- EnhancementCheckbox(FullName.c_str(), Cvar_Lock.c_str());
- Tooltip("Prevents this color from being changed upon selecting \"Randomize all\"");
-}
-
-void RainbowColor(const char* cvarName, ImVec4* colors) {
- std::string Cvar_RBM = std::string(cvarName) + "RBM";
- std::string MakeInvisible = "Rainbow##" + std::string(cvarName) + "Rainbow";
-
- EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str());
- Tooltip("Cycles through colors on a timer\nOverwrites previously chosen color");
-}
-
-void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) {
- Color_RGBA8 defaultColors;
- defaultColors.r = default_colors.x;
- defaultColors.g = default_colors.y;
- defaultColors.b = default_colors.z;
- defaultColors.a = default_colors.w;
-
- Color_RGBA8 cvarColor = CVarGetColor(cvarname, defaultColors);
-
- ColorArray.x = cvarColor.r / 255.0;
- ColorArray.y = cvarColor.g / 255.0;
- ColorArray.z = cvarColor.b / 255.0;
- ColorArray.w = cvarColor.a / 255.0;
-}
-
-void DrawFlagArray32(const std::string& name, uint32_t& flags) {
- ImGui::PushID(name.c_str());
- for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) {
- if ((flagIndex % 8) != 0) {
- ImGui::SameLine();
- }
- ImGui::PushID(flagIndex);
- uint32_t bitMask = 1 << flagIndex;
- bool flag = (flags & bitMask) != 0;
- if (ImGui::Checkbox("##check", &flag)) {
- if (flag) {
- flags |= bitMask;
- } else {
- flags &= ~bitMask;
- }
- }
- ImGui::PopID();
- }
- ImGui::PopID();
-}
-
-void DrawFlagArray16(const std::string& name, uint16_t& flags) {
- ImGui::PushID(name.c_str());
- for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) {
- if ((flagIndex % 8) != 0) {
- ImGui::SameLine();
- }
- ImGui::PushID(flagIndex);
- uint16_t bitMask = 1 << flagIndex;
- bool flag = (flags & bitMask) != 0;
- if (ImGui::Checkbox("##check", &flag)) {
- if (flag) {
- flags |= bitMask;
- } else {
- flags &= ~bitMask;
- }
- }
- ImGui::PopID();
- }
- ImGui::PopID();
-}
-
-void DrawFlagArray8(const std::string& name, uint8_t& flags) {
- ImGui::PushID(name.c_str());
- for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) {
- if ((flagIndex % 8) != 0) {
- ImGui::SameLine();
- }
- ImGui::PushID(flagIndex);
- uint8_t bitMask = 1 << flagIndex;
- bool flag = (flags & bitMask) != 0;
- if (ImGui::Checkbox("##check", &flag)) {
- if (flag) {
- flags |= bitMask;
- } else {
- flags &= ~bitMask;
- }
- }
- ImGui::PopID();
+ ImGui::SetTooltip("%s", WrappedText(text).c_str());
}
- ImGui::PopID();
}
-// V2
-
void PushStyleMenu(const ImVec4& color) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f));
- ImGui::PushStyleColor(ImGuiCol_PopupBg, UIWidgets::Colors::DarkGray);
- ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::Colors::DarkGray);
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, UIWidgets::ColorValues.at(Colors::DarkGray));
+ ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(Colors::DarkGray));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f));
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f);
}
+void PushStyleMenu(Colors color) {
+ PushStyleMenu(UIWidgets::ColorValues.at(color));
+}
+
void PopStyleMenu() {
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(4);
}
-bool BeginMenu(const char* label, const ImVec4& color) {
+bool BeginMenu(const char* label, Colors color) {
bool dirty = false;
PushStyleMenu(color);
+ ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f, 0.0f), ImVec2(FLT_MAX, FLT_MAX));
if (ImGui::BeginMenu(label)) {
dirty = true;
}
@@ -660,16 +78,20 @@ bool BeginMenu(const char* label, const ImVec4& color) {
}
void PushStyleMenuItem(const ImVec4& color) {
- ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f));
+ ImGui::PushStyleColor(ImGuiCol_HeaderHovered, color);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f));
}
+void PushStyleMenuItem(Colors color) {
+ PushStyleMenuItem(ColorValues.at(color));
+}
+
void PopStyleMenuItem() {
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
}
-bool MenuItem(const char* label, const char* shortcut, const ImVec4& color) {
+bool MenuItem(const char* label, const char* shortcut, Colors color) {
bool dirty = false;
PushStyleMenuItem(color);
if (ImGui::MenuItem(label, shortcut)) {
@@ -689,6 +111,10 @@ void PushStyleButton(const ImVec4& color) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f);
}
+void PushStyleButton(Colors color) {
+ PushStyleButton(UIWidgets::ColorValues.at(color));
+}
+
void PopStyleButton() {
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(4);
@@ -701,10 +127,10 @@ bool Button(const char* label, const ButtonOptions& options) {
PopStyleButton();
ImGui::EndDisabled();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
- strcmp(options.disabledTooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip));
- } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.tooltip));
+ !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
return dirty;
}
@@ -738,71 +164,131 @@ void PushStyleCheckbox(const ImVec4& color) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f);
}
+void PushStyleCheckbox(Colors color) {
+ PushStyleCheckbox(UIWidgets::ColorValues.at(color));
+}
+
void PopStyleCheckbox() {
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(5);
}
-bool Checkbox(const char* label, bool* value, const CheckboxOptions& options) {
- ImGui::PushID(label);
- bool dirty = false;
- float startX = ImGui::GetCursorPosX();
- std::string invisibleLabelStr = "##" + std::string(label);
- const char* invisibleLabel = invisibleLabelStr.c_str();
+void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) {
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ // Hide anything after a '##' string
+ const char* text_display_end;
+ if (hide_text_after_hash) {
+ text_display_end = ImGui::FindRenderedTextEnd(text, text_end);
+ } else {
+ if (!text_end)
+ text_end = text + strlen(text); // FIXME-OPT
+ text_display_end = text_end;
+ }
+
+ if (text != text_display_end) {
+ window->DrawList->AddText(g.Font, g.FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end);
+ if (g.LogEnabled)
+ ImGui::LogRenderedText(&pos, text, text_display_end);
+ }
+}
+
+bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) {
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
ImGui::BeginDisabled(options.disabled);
- PushStyleCheckbox(options.color);
- if (options.alignment == ComponentAlignment::Right) {
- if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far ||
- options.labelPosition == LabelPosition::None) {
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 -
- ImGui::GetStyle().ItemSpacing.x);
- } else if (options.labelPosition == LabelPosition::Above) {
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
- ImGui::Text(label);
- ImGui::NewLine();
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 -
- ImGui::GetStyle().ItemSpacing.x);
- }
- } else if (options.alignment == ComponentAlignment::Left) {
- if (options.labelPosition == LabelPosition::Above) {
- ImGui::Text(label);
+
+ bool above = options.labelPosition == LabelPosition::Above;
+ bool lpFar = options.labelPosition == LabelPosition::Far;
+ bool right = options.alignment == ComponentAlignment::Right;
+ bool none = options.labelPosition == LabelPosition::None;
+
+ std::string labelStr = (none ? "##" : "");
+ labelStr.append(_label);
+
+ const char* label = labelStr.c_str();
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
+ const float square_sz = ImGui::GetFrameHeight();
+ ImVec2 pos = window->DC.CursorPos;
+
+ if (right) {
+ float labelOffsetX = (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz);
+ if (!lpFar) {
+ pos.x += ImGui::GetContentRegionAvail().x - (label_size.x + labelOffsetX);
}
}
- dirty = ImGui::Checkbox(invisibleLabel, value);
+ float bbAboveX = lpFar ? ImGui::GetContentRegionAvail().x
+ : (label_size.x + (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz));
+ float bbAboveY = label_size.y + (above ? square_sz : 0) + (style.FramePadding.y * 2.0f);
+ const ImRect total_bb(pos, pos + ImVec2(bbAboveX, bbAboveY));
+
+ ImGui::ItemSize(total_bb, style.FramePadding.y);
+ if (!ImGui::ItemAdd(total_bb, id)) {
+ ImGui::EndDisabled();
+ return false;
+ }
+ bool hovered, held, pressed;
+ pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held);
+ if (pressed) {
+ *value = !(*value);
+ ImGui::MarkItemEdited(id);
+ }
+ PushStyleCheckbox(options.color);
+ ImVec2 checkPos = pos;
+ ImVec2 labelPos = pos;
+ if (options.labelPosition == LabelPosition::Above) {
+ checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f);
+ } else {
+ labelPos.y += (square_sz / 2) - (label_size.y / 2);
+ }
if (options.alignment == ComponentAlignment::Right) {
- if (options.labelPosition == LabelPosition::Near) {
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x -
- ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2);
- ImGui::Text(label);
- } else if (options.labelPosition == LabelPosition::Far) {
- ImGui::SameLine();
- ImGui::SetCursorPosX(startX);
- ImGui::Text(label);
- }
- } else if (options.alignment == ComponentAlignment::Left) {
- if (options.labelPosition == LabelPosition::Near) {
- ImGui::SameLine();
- ImGui::Text(label);
- } else if (options.labelPosition == LabelPosition::Far) {
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
- ImGui::Text(label);
- }
+ checkPos.x = total_bb.Max.x - square_sz;
+ } else {
+ float labelFarOffset = ImGui::GetContentRegionAvail().x - label_size.x;
+ float labelOffsetX = above ? 0 : (lpFar ? labelFarOffset : (style.ItemInnerSpacing.x * 2.0f) + square_sz);
+ labelPos.x += labelOffsetX;
}
+ const ImRect check_bb(checkPos, checkPos + ImVec2(square_sz, square_sz));
+ ImGui::RenderNavHighlight(total_bb, id);
+ ImGui::RenderFrame(check_bb.Min, check_bb.Max,
+ ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive
+ : hovered ? ImGuiCol_FrameBgHovered
+ : ImGuiCol_FrameBg),
+ true, style.FrameRounding);
+ ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark);
+ bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
+ if (mixed_value) {
+ // Undocumented tristate/mixed/indeterminate checkbox (#2644)
+ // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all
+ // widgets (not just checkbox)
+ ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
+ window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
+ } else if (*value) {
+ const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
+ ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
+ }
+ RenderText(labelPos, label, ImGui::FindRenderedTextEnd(label), true);
PopStyleCheckbox();
ImGui::EndDisabled();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
- strcmp(options.disabledTooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip));
- } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.tooltip));
+ !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
- ImGui::PopID();
- return dirty;
+ return pressed;
}
bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) {
bool dirty = false;
- bool value = (bool) CVarGetInteger(cvarName, options.defaultValue);
+ bool value = (bool)CVarGetInteger(cvarName, options.defaultValue);
if (Checkbox(label, &value, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
@@ -827,100 +313,17 @@ void PushStyleCombobox(const ImVec4& color) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f));
}
+void PushStyleCombobox(Colors color) {
+ PushStyleCombobox(UIWidgets::ColorValues.at(color));
+}
+
void PopStyleCombobox() {
ImGui::PopStyleVar(4);
ImGui::PopStyleColor(9);
}
-bool Combobox(const char* label, uint8_t* value, std::span<const char*, std::dynamic_extent> comboArray,
- const ComboboxOptions& options) {
- bool dirty = false;
- float startX = ImGui::GetCursorPosX();
- std::string invisibleLabelStr = "##" + std::string(label);
- const char* invisibleLabel = invisibleLabelStr.c_str();
- ImGui::PushID(label);
- ImGui::BeginGroup();
- ImGui::BeginDisabled(options.disabled);
- PushStyleCombobox(options.color);
- if (options.alignment == ComponentAlignment::Left) {
- if (options.labelPosition == LabelPosition::Above) {
- ImGui::Text(label);
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- } else if (options.labelPosition == LabelPosition::Near) {
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x -
- ImGui::GetStyle().ItemSpacing.x * 2);
- } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
- ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4 +
- ImGui::GetStyle().ItemSpacing.x);
- }
- } else if (options.alignment == ComponentAlignment::Right) {
- if (options.labelPosition == LabelPosition::Above) {
- ImGui::NewLine();
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
- ImGui::Text(label);
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- } else if (options.labelPosition == LabelPosition::Near) {
- ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
- float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4;
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- }
- }
- if (ImGui::BeginCombo(invisibleLabel, comboArray[*value], options.flags)) {
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
- for (uint8_t i = 0; i < comboArray.size(); i++) {
- if (strlen(comboArray[i]) > 1) {
- if (ImGui::Selectable(comboArray[i], i == *value)) {
- *value = i;
- dirty = true;
- }
- }
- }
- ImGui::PopStyleVar();
- ImGui::EndCombo();
- }
- if (options.alignment == ComponentAlignment::Left) {
- if (options.labelPosition == LabelPosition::Near) {
- ImGui::SameLine();
- ImGui::Text(label);
- } else if (options.labelPosition == LabelPosition::Far) {
- ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
- ImGui::Text(label);
- }
- } else if (options.alignment == ComponentAlignment::Right) {
- if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
- ImGui::SameLine(startX);
- ImGui::Text(label);
- }
- }
- PopStyleCombobox();
- ImGui::EndDisabled();
- ImGui::EndGroup();
- if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
- strcmp(options.disabledTooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip));
- } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.tooltip));
- }
- ImGui::PopID();
- return dirty;
-}
-
-bool CVarCombobox(const char* label, const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray,
- const ComboboxOptions& options) {
- bool dirty = false;
- uint8_t value = (uint8_t) CVarGetInteger(cvarName, options.defaultIndex);
- if (Combobox(label, &value, comboArray, options)) {
- CVarSetInteger(cvarName, value);
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
- dirty = true;
- }
- return dirty;
-}
-
-void PushStyleSlider(const ImVec4& color) {
+void PushStyleSlider(Colors color_) {
+ const ImVec4& color = UIWidgets::ColorValues.at(color_);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f));
@@ -938,7 +341,7 @@ void PopStyleSlider() {
ImGui::PopStyleColor(6);
}
-bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, const IntSliderOptions& options) {
+bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options) {
bool dirty = false;
std::string invisibleLabelStr = "##" + std::string(label);
const char* invisibleLabel = invisibleLabelStr.c_str();
@@ -958,11 +361,10 @@ bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, cons
}
}
if (options.showButtons) {
- if (Button("-", { .color = options.color, .size = Sizes::Inline }) && *value > min) {
+ if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) {
*value -= options.step;
- if (*value < min)
- *value = min;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ if (*value < options.min)
+ *value = options.min;
dirty = true;
}
ImGui::SameLine(0, 3.0f);
@@ -970,18 +372,17 @@ bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, cons
} else {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
}
- if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &min, &max, options.format, options.flags)) {
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &options.min, &options.max, options.format,
+ options.flags)) {
dirty = true;
}
if (options.showButtons) {
ImGui::SameLine(0, 3.0f);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- if (Button("+", { .color = options.color, .size = Sizes::Inline }) && *value < max) {
+ if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) {
*value += options.step;
- if (*value > max)
- *value = max;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ if (*value > options.max)
+ *value = options.max;
dirty = true;
}
}
@@ -989,20 +390,19 @@ bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, cons
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
- strcmp(options.disabledTooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip));
- } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.tooltip));
+ !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
return dirty;
}
-bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue,
- const IntSliderOptions& options) {
+bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options) {
bool dirty = false;
- int32_t value = CVarGetInteger(cvarName, defaultValue);
- if (SliderInt(label, &value, min, max, options)) {
+ int32_t value = CVarGetInteger(cvarName, options.defaultValue);
+ if (SliderInt(label, &value, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
dirty = true;
@@ -1010,13 +410,51 @@ bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t
return dirty;
}
-bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options) {
+void ClampFloat(float* value, float min, float max, float step) {
+ int ticks = 0;
+ float increment = 1.0f;
+ if (step < 1.0f) {
+ ticks++;
+ increment = 0.1f;
+ }
+ if (step < 0.1f) {
+ ticks++;
+ increment = 0.01f;
+ }
+ if (step < 0.01f) {
+ ticks++;
+ increment = 0.001f;
+ }
+ if (step < 0.001f) {
+ ticks++;
+ increment = 0.0001f;
+ }
+ if (step < 0.0001f) {
+ ticks++;
+ increment = 0.00001f;
+ }
+ if (step < 0.00001f) {
+ ticks++;
+ increment = 0.000001f;
+ }
+ int factor = 1 * std::pow(10, ticks);
+ if (*value < min) {
+ *value = min;
+ } else if (*value > max) {
+ *value = max;
+ } else {
+ int trunc = (int)std::round(*value * factor);
+ *value = (float)trunc / factor;
+ }
+}
+
+bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options) {
bool dirty = false;
std::string invisibleLabelStr = "##" + std::string(label);
const char* invisibleLabel = invisibleLabelStr.c_str();
float valueToDisplay = options.isPercentage ? *value * 100.0f : *value;
- float maxToDisplay = options.isPercentage ? max * 100.0f : max;
- float minToDisplay = options.isPercentage ? min * 100.0f : min;
+ float maxToDisplay = options.isPercentage ? options.max * 100.0f : options.max;
+ float minToDisplay = options.isPercentage ? options.min * 100.0f : options.min;
ImGui::PushID(label);
ImGui::BeginGroup();
ImGui::BeginDisabled(options.disabled);
@@ -1033,11 +471,9 @@ 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) {
+ if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) {
*value -= options.step;
- if (*value < min)
- *value = min;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ ClampFloat(value, options.min, options.max, options.step);
dirty = true;
}
ImGui::SameLine(0, 3.0f);
@@ -1048,17 +484,15 @@ 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;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ ClampFloat(value, options.min, options.max, options.step);
dirty = true;
}
if (options.showButtons) {
ImGui::SameLine(0, 3.0f);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
- if (Button("+", { .color = options.color, .size = Sizes::Inline }) && *value < max) {
+ if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) {
*value += options.step;
- if (*value > max)
- *value = max;
- Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ ClampFloat(value, options.min, options.max, options.step);
dirty = true;
}
}
@@ -1066,24 +500,133 @@ bool SliderFloat(const char* label, float* value, float min, float max, const Fl
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
- strcmp(options.disabledTooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip));
- } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) {
- ImGui::SetTooltip("%s", WrappedText(options.tooltip));
+ !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
return dirty;
}
-bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue,
- const FloatSliderOptions& options) {
+bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options) {
bool dirty = false;
- float value = CVarGetFloat(cvarName, defaultValue);
- if (SliderFloat(label, &value, min, max, options)) {
+ float value = CVarGetFloat(cvarName, options.defaultValue);
+ if (SliderFloat(label, &value, options)) {
CVarSetFloat(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
dirty = true;
}
return dirty;
}
+
+bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor) {
+ Color_RGBA8 color = CVarGetColor(cvarName, defaultColor);
+ ImVec4 colorVec = ImVec4(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f);
+ bool changed = false;
+ PushStyleCombobox(Colors::Gray);
+ if (ImGui::ColorEdit3(label, (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoBorder)) {
+ color.r = (uint8_t)(colorVec.x * 255.0f);
+ color.g = (uint8_t)(colorVec.y * 255.0f);
+ color.b = (uint8_t)(colorVec.z * 255.0f);
+ color.a = (uint8_t)(colorVec.w * 255.0f);
+ CVarSetColor(cvarName, color);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ changed = true;
+ }
+ PopStyleCombobox();
+ return changed;
+}
+
+void DrawFlagArray32(const std::string& name, uint32_t& flags) {
+ ImGui::PushID(name.c_str());
+ for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) {
+ if ((flagIndex % 8) != 0) {
+ ImGui::SameLine();
+ }
+ ImGui::PushID(flagIndex);
+ uint32_t bitMask = 1 << flagIndex;
+ bool flag = (flags & bitMask) != 0;
+ std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex);
+ if (UIWidgets::Checkbox(label.c_str(), &flag,
+ CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) {
+ if (flag) {
+ flags |= bitMask;
+ } else {
+ flags &= ~bitMask;
+ }
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+}
+
+void DrawFlagArray16(const std::string& name, uint16_t& flags) {
+ ImGui::PushID(name.c_str());
+ for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) {
+ if ((flagIndex % 8) != 0) {
+ ImGui::SameLine();
+ }
+ ImGui::PushID(flagIndex);
+ uint16_t bitMask = 1 << flagIndex;
+ bool flag = (flags & bitMask) != 0;
+ std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex);
+ if (UIWidgets::Checkbox(label.c_str(), &flag,
+ CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) {
+ if (flag) {
+ flags |= bitMask;
+ } else {
+ flags &= ~bitMask;
+ }
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+}
+
+void DrawFlagArray8(const std::string& name, uint8_t& flags) {
+ ImGui::PushID(name.c_str());
+ for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) {
+ if ((flagIndex % 8) != 0) {
+ ImGui::SameLine();
+ }
+ ImGui::PushID(flagIndex);
+ uint8_t bitMask = 1 << flagIndex;
+ bool flag = (flags & bitMask) != 0;
+ std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex);
+ if (UIWidgets::Checkbox(label.c_str(), &flag,
+ CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) {
+ if (flag) {
+ flags |= bitMask;
+ } else {
+ flags &= ~bitMask;
+ }
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+}
+
+void DrawFlagArray8Mask(const std::string& name, uint8_t& flags) {
+ ImGui::PushID(name.c_str());
+ for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) {
+ if ((flagIndex % 8) != 0) {
+ ImGui::SameLine();
+ }
+ ImGui::PushID(flagIndex);
+ uint8_t bitMask = 1 << flagIndex;
+ bool flag = (flags & bitMask) != 0;
+ std::string label = fmt::format("0x{:02X} ({})", bitMask, flagIndex);
+ if (UIWidgets::Checkbox(label.c_str(), &flag,
+ CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) {
+ if (flag) {
+ flags |= bitMask;
+ } else {
+ flags &= ~bitMask;
+ }
+ }
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+}
} // namespace UIWidgets
diff --git a/src/port/ui/UIWidgets.h b/src/port/ui/UIWidgets.h
index 93a7a9b2d..079c66869 100644
--- a/src/port/ui/UIWidgets.h
+++ b/src/port/ui/UIWidgets.h
@@ -1,231 +1,711 @@
-#pragma once
+#ifndef UIWidgets_hpp
+#define UIWidgets_hpp
#include <string>
#include <vector>
#include <span>
-#include <cstdint>
+#include <stdint.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <libultraship/libultraship.h>
+#include <unordered_map>
+#include "port/ShipUtils.h"
namespace UIWidgets {
-struct TextFilters {
- static int FilterNumbers(ImGuiInputTextCallbackData* data) {
- if (data->EventChar < 256 && strchr("1234567890", (char) data->EventChar)) {
- return 0;
+ using SectionFunc = void(*)();
+
+ struct TextFilters {
+ static int FilterNumbers(ImGuiInputTextCallbackData* data) {
+ if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) {
+ return 0;
+ }
+ return 1;
+ }
+
+ static int FilterAlphaNum(ImGuiInputTextCallbackData* data) {
+ const char* alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789";
+ if (data->EventChar < 256 && strchr(alphanum, (char)data->EventChar)) {
+ return 0;
+ }
+ return 1;
+ }
+ };
+
+ std::string WrappedText(const char* text, unsigned int charactersPerLine = 60);
+ std::string WrappedText(const std::string& text, unsigned int charactersPerLine = 60);
+ void Tooltip(const char* text);
+
+ // mostly in order for colors usable by the menu without custom text color
+ enum Colors {
+ Red,
+ DarkRed,
+ Orange,
+ Green,
+ DarkGreen,
+ LightBlue,
+ Blue,
+ DarkBlue,
+ Indigo,
+ Violet,
+ Purple,
+ Brown,
+ Gray,
+ DarkGray,
+ // not suitable for menu theme use
+ Pink,
+ Yellow,
+ Cyan,
+ Black,
+ LightGray,
+ White,
+ NoColor
+ };
+
+ const std::unordered_map<Colors, ImVec4> ColorValues = {
+ { Colors::Pink, ImVec4(0.87f, 0.3f, 0.87f, 1.0f) },
+ { Colors::Red, ImVec4(0.55f, 0.0f, 0.0f, 1.0f) },
+ { Colors::DarkRed, ImVec4(0.3f, 0.0f, 0.0f, 1.0f) },
+ { Colors::Orange, ImVec4(0.85f, 0.55f, 0.0f, 1.0f) },
+ { Colors::Yellow, ImVec4(0.95f, 0.95f, 0.0f, 1.0f) },
+ { Colors::Green, ImVec4(0.0f, 0.55f, 0.0f, 1.0f) },
+ { Colors::DarkGreen, ImVec4(0.0f, 0.3f, 0.0f, 1.0f) },
+ { Colors::Cyan, ImVec4(0.0f, 0.9f, 0.9f, 1.0f) },
+ { Colors::LightBlue, ImVec4(0.0f, 0.24f, 0.8f, 1.0f) },
+ { Colors::Blue, ImVec4(0.08f, 0.03f, 0.65f, 1.0f) },
+ { Colors::DarkBlue, ImVec4(0.03f, 0.0f, 0.5f, 1.0f) },
+ { Colors::Indigo, ImVec4(0.35f, 0.0f, 0.87f, 1.0f) },
+ { Colors::Violet, ImVec4(0.5f, 0.0f, 0.9f, 1.0f) },
+ { Colors::Purple, ImVec4(0.31f, 0.0f, 0.67f, 1.0f) },
+ { Colors::Brown, ImVec4(0.37f, 0.18f, 0.0f, 1.0f) },
+ { Colors::LightGray, ImVec4(0.75f, 0.75f, 0.75f, 1.0f) },
+ { Colors::Gray, ImVec4(0.45f, 0.45f, 0.45f, 1.0f) },
+ { Colors::DarkGray, ImVec4(0.15f, 0.15f, 0.15f, 1.0f) },
+ { Colors::Black, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)},
+ { Colors::White, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) },
+ { Colors::NoColor, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)},
+ };
+
+ namespace Sizes {
+ const ImVec2 Inline = ImVec2(0.0f, 0.0f);
+ const ImVec2 Fill = ImVec2(-1.0f, 0.0f);
+ }
+
+ enum LabelPosition {
+ Near,
+ Far,
+ Above,
+ None,
+ Within,
+ };
+
+ enum ComponentAlignment {
+ Left,
+ Right,
+ };
+
+ struct WidgetOptions{
+ const char* tooltip = "";
+ bool disabled = false;
+ const char* disabledTooltip = "";
+ Colors color = Colors::NoColor;
+
+ WidgetOptions& Color(Colors color_) {
+ color = color = color_;
+ return *this;
+ }
+ WidgetOptions& Tooltip(const char* tooltip_) {
+ tooltip = tooltip_;
+ return *this;
+ }
+ WidgetOptions& Disabled(bool disabled_) {
+ disabled = disabled_;
+ return *this;
+ }
+ };
+
+ struct ButtonOptions : WidgetOptions {
+ ImVec2 size = Sizes::Fill;
+ Colors color = Colors::Gray;
+
+ ButtonOptions& Size(ImVec2 size_) {
+ size = size_;
+ return *this;
+ }
+ ButtonOptions& Tooltip(const char* tooltip_) {
+ WidgetOptions::tooltip = tooltip_;
+ return *this;
+ }
+ ButtonOptions& Color(Colors color_) {
+ WidgetOptions::color = color = color_;
+ return *this;
+ }
+ };
+
+ struct CheckboxOptions : WidgetOptions {
+ bool defaultValue = false; // Only applicable to CVarCheckbox
+ ComponentAlignment alignment = ComponentAlignment::Left;
+ LabelPosition labelPosition = LabelPosition::Near;
+ Colors color = WidgetOptions::color = Colors::LightBlue;
+
+ CheckboxOptions& DefaultValue(bool defaultValue_) {
+ defaultValue = defaultValue_;
+ return *this;
+ }
+ CheckboxOptions& ComponentAlignment(ComponentAlignment alignment_) {
+ alignment = alignment_;
+ return *this;
+ }
+ CheckboxOptions& LabelPosition(LabelPosition labelPosition_) {
+ labelPosition = labelPosition_;
+ return *this;
+ }
+ CheckboxOptions& Tooltip(const char* tooltip_) {
+ WidgetOptions::tooltip = tooltip_;
+ return *this;
+ }
+ CheckboxOptions& Color(Colors color_) {
+ WidgetOptions::color = color = color_;
+ return *this;
+ }
+ };
+
+ struct ComboboxOptions : WidgetOptions {
+ std::unordered_map<int32_t, const char*> comboMap = {};
+ uint32_t defaultIndex = 0; // Only applicable to CVarCombobox
+ ComponentAlignment alignment = ComponentAlignment::Left;
+ LabelPosition labelPosition = LabelPosition::Above;
+ ImGuiComboFlags flags = 0;
+ Colors color = Colors::LightBlue;
+
+ ComboboxOptions& ComboMap(std::unordered_map<int32_t, const char*> comboMap_) {
+ comboMap = comboMap_;
+ return *this;
+ }
+ ComboboxOptions& DefaultIndex(uint32_t defaultIndex_) {
+ defaultIndex = defaultIndex_;
+ return *this;
+ }
+ ComboboxOptions& ComponentAlignment(ComponentAlignment alignment_) {
+ alignment = alignment_;
+ return *this;
+ }
+ ComboboxOptions& LabelPosition(LabelPosition labelPosition_) {
+ labelPosition = labelPosition_;
+ return *this;
+ }
+ ComboboxOptions& Tooltip(const char* tooltip_) {
+ WidgetOptions::tooltip = tooltip_;
+ return *this;
+ }
+ ComboboxOptions& Color(Colors color_) {
+ WidgetOptions::color = color = color_;
+ return *this;
+ }
+ };
+
+ struct IntSliderOptions : WidgetOptions {
+ bool showButtons = true;
+ const char* format = "%d";
+ int32_t step = 1;
+ int32_t min = 1;
+ int32_t max = 10;
+ int32_t defaultValue = 1;
+ ComponentAlignment alignment = ComponentAlignment::Left;
+ LabelPosition labelPosition = LabelPosition::Above;
+ Colors color = Colors::Gray;
+ ImGuiSliderFlags flags = 0;
+
+ IntSliderOptions& ShowButtons(bool showButtons_) {
+ showButtons = showButtons_;
+ return *this;
+ }
+ IntSliderOptions& Format(const char* format_) {
+ format = format_;
+ return *this;
+ }
+ IntSliderOptions& Step(int32_t step_) {
+ step = step_;
+ return *this;
+ }
+ IntSliderOptions& Min(int32_t min_) {
+ min = min_;
+ return *this;
+ }
+ IntSliderOptions& Max(int32_t max_) {
+ max = max_;
+ return *this;
+ }
+ IntSliderOptions& DefaultValue(int32_t defaultValue_) {
+ defaultValue = defaultValue_;
+ return *this;
+ }
+ IntSliderOptions& ComponentAlignment(ComponentAlignment alignment_) {
+ alignment = alignment_;
+ return *this;
+ }
+ IntSliderOptions& LabelPosition(LabelPosition labelPosition_) {
+ labelPosition = labelPosition_;
+ return *this;
+ }
+ IntSliderOptions& Tooltip(const char* tooltip_) {
+ WidgetOptions::tooltip = tooltip_;
+ return *this;
+ }
+ IntSliderOptions& Color(Colors color_) {
+ WidgetOptions::color = color = color_;
+ return *this;
+ }
+ };
+
+ struct FloatSliderOptions : WidgetOptions {
+ bool showButtons = true;
+ const char* format = "%f";
+ float step = 0.01f;
+ float min = 0.01f;
+ float max = 10.0f;
+ float defaultValue = 1.0f;
+ bool isPercentage = false; // Multiplies visual value by 100
+ ComponentAlignment alignment = ComponentAlignment::Left;
+ LabelPosition labelPosition = LabelPosition::Above;
+ Colors color = Colors::Gray;
+ ImGuiSliderFlags flags = 0;
+
+ FloatSliderOptions& ShowButtons(bool showButtons_) {
+ showButtons = showButtons_;
+ return *this;
+ }
+ FloatSliderOptions& Format(const char* format_) {
+ format = format_;
+ return *this;
+ }
+ FloatSliderOptions& Step(float step_) {
+ step = step_;
+ return *this;
}
- return 1;
+ FloatSliderOptions& Min(float min_) {
+ min = min_;
+ return *this;
+ }
+ FloatSliderOptions& Max(float max_) {
+ max = max_;
+ return *this;
+ }
+ FloatSliderOptions& DefaultValue(float defaultValue_) {
+ defaultValue = defaultValue_;
+ return *this;
+ }
+ FloatSliderOptions& ComponentAlignment(ComponentAlignment alignment_) {
+ alignment = alignment_;
+ return *this;
+ }
+ FloatSliderOptions& LabelPosition(LabelPosition labelPosition_) {
+ labelPosition = labelPosition_;
+ return *this;
+ }
+ FloatSliderOptions& IsPercentage(bool isPercentage_ = true) {
+ isPercentage = isPercentage_;
+ format = "%.0f";
+ min = 0.0f;
+ max = 1.0f;
+ return *this;
+ }
+ FloatSliderOptions& Tooltip(const char* tooltip_) {
+ WidgetOptions::tooltip = tooltip_;
+ return *this;
+ }
+ FloatSliderOptions& Color(Colors color_) {
+ WidgetOptions::color = color = color_;
+ return *this;
+ }
+ };
+
+ void PushStyleMenu(const ImVec4& color);
+ void PushStyleMenu(Colors color = Colors::LightBlue);
+ void PopStyleMenu();
+ bool BeginMenu(const char* label, Colors color = Colors::LightBlue);
+
+ void PushStyleMenuItem(const ImVec4& color);
+ void PushStyleMenuItem(Colors color = Colors::LightBlue);
+ void PopStyleMenuItem();
+ bool MenuItem(const char* label, const char* shortcut = NULL, Colors color = Colors::LightBlue);
+
+ void PushStyleButton(const ImVec4& color);
+ void PushStyleButton(Colors color = Colors::Gray);
+ void PopStyleButton();
+ bool Button(const char* label, const ButtonOptions& options = {});
+ bool WindowButton(const char* label, const char* cvarName, std::shared_ptr<Ship::GuiWindow> windowPtr, const ButtonOptions& options = {});
+
+ void PushStyleCheckbox(const ImVec4& color);
+ void PushStyleCheckbox(Colors color = Colors::LightBlue);
+ void PopStyleCheckbox();
+ void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash);
+ bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {});
+ bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {});
+
+ void PushStyleCombobox(const ImVec4& color);
+ void PushStyleCombobox(Colors color = Colors::LightBlue);
+ void PopStyleCombobox();
+
+ /*using ComboVariant = std::variant<const std::unordered_map<int32_t, const char*>&, const std::vector<const char*>&>;
+
+ bool Combobox(const char* label, int32_t* value, ComboVariant comboSource, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ float startX = ImGui::GetCursorPosX();
+ std::string invisibleLabelStr = "##" + std::string(label);
+ const char* invisibleLabel = invisibleLabelStr.c_str();
+ ImGui::PushID(label);
+ ImGui::BeginGroup();
+ ImGui::BeginDisabled(options.disabled);
+ PushStyleCombobox(options.color);
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::NewLine();
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4;
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ }
+ }
+ if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
+ for (const auto& pair : comboMap) {
+ if (strlen(pair.second) > 1) {
+ if (ImGui::Selectable(pair.second, pair.first == *value)) {
+ *value = pair.first;
+ dirty = true;
+ }
+ }
+ }
+ ImGui::PopStyleVar();
+ ImGui::EndCombo();
+ }
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine();
+ ImGui::Text("%s", label);
+ } else if (options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(startX);
+ ImGui::Text("%s", label);
+ }
+ }
+ PopStyleCombobox();
+ ImGui::EndDisabled();
+ ImGui::EndGroup();
+ if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
+ }
+ ImGui::PopID();
+ return dirty;
}
- static int FilterAlphaNum(ImGuiInputTextCallbackData* data) {
- const char* alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789";
- if (data->EventChar < 256 && strchr(alphanum, (char) data->EventChar)) {
- return 0;
+ bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
+ if (Combobox(label, &value, comboSource, options)) {
+ CVarSetInteger(cvarName, value);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ ShipInit::Init(cvarName);
+ dirty = true;
+ }
+ return dirty;
+ }*/
+
+ template <typename T>
+ bool Combobox(const char* label, T* value, const std::unordered_map<T, const char*>& comboMap, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ float startX = ImGui::GetCursorPosX();
+ std::string invisibleLabelStr = "##" + std::string(label);
+ const char* invisibleLabel = invisibleLabelStr.c_str();
+ ImGui::PushID(label);
+ ImGui::BeginGroup();
+ ImGui::BeginDisabled(options.disabled);
+ PushStyleCombobox(options.color);
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::NewLine();
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4;
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ }
+ }
+ if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
+ for (const auto& pair : comboMap) {
+ if (strlen(pair.second) > 1) {
+ if (ImGui::Selectable(pair.second, pair.first == *value)) {
+ *value = pair.first;
+ dirty = true;
+ }
+ }
+ }
+ ImGui::PopStyleVar();
+ ImGui::EndCombo();
+ }
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine();
+ ImGui::Text("%s", label);
+ } else if (options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(startX);
+ ImGui::Text("%s", label);
+ }
}
- return 1;
+ PopStyleCombobox();
+ ImGui::EndDisabled();
+ ImGui::EndGroup();
+ if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
+ }
+ ImGui::PopID();
+ return dirty;
}
-};
-
-// MARK: - Enums
-
-enum class CheckboxGraphics { Cross, Checkmark, None };
-constexpr float maxSliderWidth = 260.0f;
-#ifdef __SWITCH__
-constexpr float sliderButtonWidth = 42.0f;
-#elif defined(__WIIU__)
-constexpr float sliderButtonWidth = 60.0f;
-#else
-constexpr float sliderButtonWidth = 30.0f;
-#endif
-
-char* WrappedText(const char* text, unsigned int charactersPerLine = 60);
-char* WrappedText(const std::string& text, unsigned int charactersPerLine);
-
-void SetLastItemHoverText(const std::string& text);
-void SetLastItemHoverText(const char* text);
-
-void InsertHelpHoverText(const std::string& text);
-void InsertHelpHoverText(const char* text);
-
-void Tooltip(const char* text);
-void Spacer(float height);
-void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f,
- float extraVerticalBottomPadding = 0.0f);
-
-void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);
-bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic);
-
-void ReEnableComponent(const char* disabledTooltipText);
-void DisableComponent(const float alpha);
-
-bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false,
- const char* disabledTooltipText = "",
- CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false);
-bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true,
- bool disabled = false, const char* disabledTooltipText = "",
- CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false);
-
-bool EnhancementCombobox(const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray,
- uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "",
- uint8_t disabledValue = -1);
-bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName,
- std::span<const char*, std::dynamic_extent> comboArray,
- uint8_t defaultIndex, bool disabled = false,
- const char* disabledTooltipText = "", uint8_t disabledValue = -1);
-
-void PaddedText(const char* text, bool padTop = true, bool padBottom = true);
-
-bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format,
- int defaultValue = 0, bool PlusMinusButton = true, bool disabled = false,
- const char* disabledTooltipText = "");
-bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max,
- const char* format, int defaultValue = 0, bool PlusMinusButton = true,
- bool padTop = true, bool padBottom = true, bool disabled = false,
- const char* disabledTooltipText = "");
-bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
- const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true,
- bool disabled = false, const char* disabledTooltipText = "");
-bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
- const char* format, float defaultValue, bool isPercentage,
- bool PlusMinusButton = true, bool padTop = true, bool padBottom = true,
- bool disabled = false, const char* disabledTooltipText = "");
-
-bool EnhancementRadioButton(const char* text, const char* cvarName, int id);
-
-bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha);
-bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors);
-void DrawLockColorCheckbox(const char* cvarName);
-void RainbowColor(const char* cvarName, ImVec4* colors);
-
-void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha);
-bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors,
- bool allow_rainbow = true, bool has_alpha = false, bool TitleSameLine = false);
-
-void DrawFlagArray32(const std::string& name, uint32_t& flags);
-void DrawFlagArray16(const std::string& name, uint16_t& flags);
-void DrawFlagArray8(const std::string& name, uint8_t& flags);
-
-// V2
-namespace Colors {
-const ImVec4 White = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
-const ImVec4 Gray = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
-const ImVec4 DarkGray = ImVec4(0.1f, 0.1f, 0.1f, 1.0f);
-const ImVec4 Indigo = ImVec4(0.24f, 0.31f, 0.71f, 1.0f);
-const ImVec4 Red = ImVec4(0.5f, 0.0f, 0.0f, 1.0f);
-const ImVec4 DarkRed = ImVec4(0.3f, 0.0f, 0.0f, 1.0f);
-const ImVec4 LightGreen = ImVec4(0.0f, 0.7f, 0.0f, 1.0f);
-const ImVec4 Green = ImVec4(0.0f, 0.5f, 0.0f, 1.0f);
-const ImVec4 DarkGreen = ImVec4(0.0f, 0.3f, 0.0f, 1.0f);
-const ImVec4 Yellow = ImVec4(1.0f, 0.627f, 0.0f, 1.0f);
-}; // namespace Colors
-
-namespace Sizes {
-const ImVec2 Inline = ImVec2(0.0f, 0.0f);
-const ImVec2 Fill = ImVec2(-1.0f, 0.0f);
-} // namespace Sizes
-
-enum LabelPosition {
- Near,
- Far,
- Above,
- None,
- Within,
-};
-
-enum ComponentAlignment {
- Left,
- Right,
-};
-
-void PushStyleMenu(const ImVec4& color = Colors::Indigo);
-void PopStyleMenu();
-bool BeginMenu(const char* label, const ImVec4& color = Colors::Indigo);
-
-void PushStyleMenuItem(const ImVec4& color = Colors::Indigo);
-void PopStyleMenuItem();
-bool MenuItem(const char* label, const char* shortcut = NULL, const ImVec4& color = Colors::Indigo);
-
-struct ButtonOptions {
- const ImVec4 color = Colors::Gray;
- const ImVec2 size = Sizes::Fill;
- const char* tooltip = "";
- bool disabled = false;
- const char* disabledTooltip = "";
-};
-
-void PushStyleButton(const ImVec4& color = Colors::Gray);
-void PopStyleButton();
-bool Button(const char* label, const ButtonOptions& options = {});
-bool WindowButton(const char* label, const char* cvarName, std::shared_ptr<Ship::GuiWindow> windowPtr,
- const ButtonOptions& options = {});
-
-struct CheckboxOptions {
- const ImVec4 color = Colors::Indigo;
- const char* tooltip = "";
- bool disabled = false;
- const char* disabledTooltip = "";
- bool defaultValue = false; // Only applicable to CVarCheckbox
- ComponentAlignment alignment = ComponentAlignment::Left;
- LabelPosition labelPosition = LabelPosition::Near;
-};
-
-void PushStyleCheckbox(const ImVec4& color = Colors::Indigo);
-void PopStyleCheckbox();
-bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {});
-bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {});
-
-struct ComboboxOptions {
- const ImVec4 color = Colors::Indigo;
- const char* tooltip = "";
- bool disabled = false;
- const char* disabledTooltip = "";
- uint32_t defaultIndex = 0; // Only applicable to CVarCombobox
- ComponentAlignment alignment = ComponentAlignment::Left;
- LabelPosition labelPosition = LabelPosition::Above;
- ImGuiComboFlags flags = 0;
-};
-
-void PushStyleCombobox(const ImVec4& color = Colors::Indigo);
-void PopStyleCombobox();
-bool Combobox(const char* label, uint8_t* value, std::span<const char*, std::dynamic_extent> comboArray,
- const ComboboxOptions& options = {});
-bool CVarCombobox(const char* label, const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray,
- const ComboboxOptions& options = {});
-
-struct IntSliderOptions {
- const ImVec4 color = Colors::Gray;
- const char* tooltip = "";
- bool disabled = false;
- const char* disabledTooltip = "";
- bool showButtons = true;
- ImGuiSliderFlags flags = 0;
- const char* format = "%d";
- const uint32_t step = 1;
- ComponentAlignment alignment = ComponentAlignment::Left;
- LabelPosition labelPosition = LabelPosition::Above;
-};
-
-struct FloatSliderOptions {
- const ImVec4 color = Colors::Gray;
- const char* tooltip = "";
- bool disabled = false;
- const char* disabledTooltip = "";
- bool showButtons = true;
- ImGuiSliderFlags flags = 0;
- const char* format = "%f";
- const float step = 0.01f;
- bool isPercentage = false; // Multiplies visual value by 100
- ComponentAlignment alignment = ComponentAlignment::Left;
- LabelPosition labelPosition = LabelPosition::Above;
-};
-
-void PushStyleSlider(const ImVec4& color = Colors::Indigo);
-void PopStyleSlider();
-bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, const IntSliderOptions& options = {});
-bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue,
- const IntSliderOptions& options = {});
-bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options = {});
-bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue,
- const FloatSliderOptions& options = {});
-} // namespace UIWidgets
+
+ template <typename T = size_t>
+ bool Combobox(const char* label, T* value, const std::vector<const char*>& comboVector, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ float startX = ImGui::GetCursorPosX();
+ size_t currentValueIndex = static_cast<size_t>(*value);
+ std::string invisibleLabelStr = "##" + std::string(label);
+ const char* invisibleLabel = invisibleLabelStr.c_str();
+ ImGui::PushID(label);
+ ImGui::BeginGroup();
+ ImGui::BeginDisabled(options.disabled);
+ PushStyleCombobox(options.color);
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::Text(label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::NewLine();
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text(label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ float width = ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4;
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ }
+ }
+ if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex), options.flags)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
+ for (size_t i = 0; i < comboVector.size(); ++i) {
+ auto newValue = static_cast<T>(i);
+ if (strlen(comboVector.at(i)) > 1) {
+ if (ImGui::Selectable(comboVector.at(i), newValue == *value)) {
+ *value = newValue;
+ dirty = true;
+ }
+ }
+ }
+ ImGui::PopStyleVar();
+ ImGui::EndCombo();
+ }
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine();
+ ImGui::Text(label);
+ } else if (options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text(label);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(startX);
+ ImGui::Text(label);
+ }
+ }
+ PopStyleCombobox();
+ ImGui::EndDisabled();
+ ImGui::EndGroup();
+ if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
+ }
+ ImGui::PopID();
+ return dirty;
+ }
+
+ template <typename T = size_t, size_t N>
+ bool Combobox(const char* label, T* value, const char* (&comboArray)[N], const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ float startX = ImGui::GetCursorPosX();
+ size_t currentValueIndex = static_cast<size_t>(*value);
+ if (currentValueIndex >= N) {
+ currentValueIndex = 0;
+ }
+ std::string invisibleLabelStr = "##" + std::string(label);
+ const char* invisibleLabel = invisibleLabelStr.c_str();
+ ImGui::PushID(label);
+ ImGui::BeginGroup();
+ ImGui::BeginDisabled(options.disabled);
+ PushStyleCombobox(options.color);
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Above) {
+ ImGui::NewLine();
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
+ float width = ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4;
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ }
+ }
+ if (ImGui::BeginCombo(invisibleLabel, comboArray[currentValueIndex], options.flags)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
+ for (size_t i = 0; i < N; ++i) {
+ auto newValue = static_cast<T>(i);
+ if (strlen(comboArray[i]) > 1) {
+ if (ImGui::Selectable(comboArray[i], newValue == *value)) {
+ *value = newValue;
+ dirty = true;
+ }
+ }
+ }
+ ImGui::PopStyleVar();
+ ImGui::EndCombo();
+ }
+ if (options.alignment == ComponentAlignment::Left) {
+ if (options.labelPosition == LabelPosition::Near) {
+ ImGui::SameLine();
+ ImGui::Text("%s", label);
+ } else if (options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
+ ImGui::Text("%s", label);
+ }
+ } else if (options.alignment == ComponentAlignment::Right) {
+ if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
+ ImGui::SameLine(startX);
+ ImGui::Text("%s", label);
+ }
+ }
+ PopStyleCombobox();
+ ImGui::EndDisabled();
+ ImGui::EndGroup();
+ if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
+ } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
+ ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
+ }
+ ImGui::PopID();
+ return dirty;
+ }
+
+ template <typename T = int32_t>
+ bool CVarCombobox(const char* label, const char* cvarName, const std::unordered_map<T, const char*>& comboMap, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
+ if (Combobox<T>(label, &value, comboMap, options)) {
+ CVarSetInteger(cvarName, value);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ dirty = true;
+ }
+ return dirty;
+ }
+
+ template <typename T = int32_t>
+ bool CVarCombobox(const char* label, const char* cvarName, const std::vector<const char*>& comboVector, const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
+ if (Combobox<T>(label, &value, comboVector, options)) {
+ CVarSetInteger(cvarName, value);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ dirty = true;
+ }
+ return dirty;
+ }
+
+ template <typename T = int32_t, size_t N>
+ bool CVarCombobox(const char* label, const char* cvarName, const char* (&comboArray)[N], const ComboboxOptions& options = {}) {
+ bool dirty = false;
+ int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
+ if (Combobox<T>(label, &value, comboArray, options)) {
+ CVarSetInteger(cvarName, value);
+ Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
+ dirty = true;
+ }
+ return dirty;
+ }
+
+ void PushStyleSlider(Colors color = Colors::LightBlue);
+ void PopStyleSlider();
+ bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options = {});
+ bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options = {});
+ bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options = {});
+ bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options = {});
+ bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor);
+ void DrawFlagArray32(const std::string& name, uint32_t& flags);
+ void DrawFlagArray16(const std::string& name, uint16_t& flags);
+ void DrawFlagArray8(const std::string& name, uint8_t& flags);
+ void DrawFlagArray8Mask(const std::string& name, uint8_t& flags);
+}
+
+#endif /* UIWidgets_hpp */