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>
This commit is contained in:
parent
301bbd3cd9
commit
695879c4cb
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
# kept for future reference
|
||||
#set(CVAR_VSYNC_ENABLED "${CVAR_PREFIX_SETTING}.VsyncEnabled")
|
||||
include("libultraship/cmake/cvars.cmake")
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
bool IsPlayerValid(const char* string) {
|
||||
return string != NULL && (strncmp(string, "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC", 8) != 0);
|
||||
}
|
||||
|
||||
ImGui::Text("Controller mode is not configured yet.");
|
||||
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));
|
||||
|
||||
const char* items[] = { "Mouse/Keyboard", "Controller" };
|
||||
static int current_item = 0;
|
||||
if (ImGui::Combo("Dropdown", ¤t_item, items, IM_ARRAYSIZE(items))) {
|
||||
gFreecamControllerType = current_item;
|
||||
}
|
||||
//mPortMenu->AddWidget(path, "Controller mode is not configured yet.", WIDGET_TEXT);
|
||||
|
||||
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" });
|
||||
//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));
|
||||
|
||||
ImGui::Spacing();
|
||||
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"}}));
|
||||
|
||||
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")) {};
|
||||
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"));
|
||||
|
||||
ImGui::Spacing();
|
||||
mPortMenu->AddWidget(path, "Target Player", WIDGET_TEXT);
|
||||
mPortMenu->AddWidget(path, "None", WIDGET_BUTTON)
|
||||
.Callback([](WidgetInfo& info) { fTargetPlayer = false; });
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
});
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
void FreecamWindow::UpdateElement() {
|
||||
}
|
||||
static RegisterMenuInitFunc initFunc(RegisterFreecamWidgets);
|
||||
|
||||
} // namespace Freecam
|
||||
} // namespace GameUI
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", ¤tAudioBackend, audioBackendsMap, options)) {
|
||||
Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(currentAudioBackend);
|
||||
}
|
||||
} break;
|
||||
case WIDGET_VIDEO_BACKEND: {
|
||||
UIWidgets::ComboboxOptions options = {};
|
||||
options.color = menuThemeIndex;
|
||||
options.tooltip = "Sets the renderer API used by the game.";
|
||||
options.disabled = availableWindowBackends->size() <= 1;
|
||||
options.disabledTooltip = "Only one renderer API is available on this platform.";
|
||||
if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap,
|
||||
options)) {
|
||||
Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id",
|
||||
(int32_t)(configWindowBackend));
|
||||
Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
|
||||
windowBackendsMap.at(configWindowBackend));
|
||||
Ship::Context::GetInstance()->GetConfig()->Save();
|
||||
UpdateWindowBackendObjects();
|
||||
}
|
||||
} break;
|
||||
case WIDGET_SEPARATOR: {
|
||||
ImGui::Separator();
|
||||
} break;
|
||||
case WIDGET_SEPARATOR_TEXT: {
|
||||
if (widget.options->color != UIWidgets::Colors::NoColor) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
|
||||
}
|
||||
ImGui::SeparatorText(widget.name.c_str());
|
||||
if (widget.options->color != UIWidgets::Colors::NoColor) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
} break;
|
||||
case WIDGET_TEXT: {
|
||||
if (widget.options->color != UIWidgets::Colors::NoColor) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
|
||||
}
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextWrapped("%s", widget.name.c_str());
|
||||
if (widget.options->color != UIWidgets::Colors::NoColor) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
} break;
|
||||
case WIDGET_COMBOBOX: {
|
||||
int32_t* pointer = std::get<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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
|
||||
if (!gNetwork.isConnected) {
|
||||
|
||||
static char username[NETWORK_USERNAME_LENGTH] = "TestUser";
|
||||
|
||||
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
|
||||
|
||||
ImGui::Text("Host:");
|
||||
ImGui::InputText("##edit3", addressCopy, IM_ARRAYSIZE(addressCopy));
|
||||
|
||||
if (ImGui::Button("Connect!")) {
|
||||
char* ip = NULL;
|
||||
uint16_t port = 0;
|
||||
|
||||
char* token = strtok(addressCopy, ":");
|
||||
ip = token;
|
||||
|
||||
token = strtok(NULL, ":");
|
||||
if (token != NULL) {
|
||||
port = (uint16_t) atoi(token);
|
||||
}
|
||||
|
||||
gNetwork.enabled = true;
|
||||
ConnectToServer(ip, port, username);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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::Spacing();
|
||||
ImGui::Text("-- %s --", gCupNames[sCup]);
|
||||
|
||||
if (ImGui::Button("test", ImVec2(-1, 0))) {
|
||||
// Add disconnect logic here
|
||||
cameras[0].playerId = 2;
|
||||
D_800DC5EC->player = &gPlayers[2];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* Stick UI to the bottom of the panel */
|
||||
|
||||
float windowHeight = ImGui::GetWindowHeight();
|
||||
|
||||
// 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
|
||||
|
||||
char buttonLabel[32];
|
||||
|
||||
if (sReadyUpBool) {
|
||||
strcpy(buttonLabel, "Lets Go!");
|
||||
} else {
|
||||
strcpy(buttonLabel, "Ready Up!");
|
||||
}
|
||||
|
||||
// First button (Ready Up / Let's Go)
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1, 0))) {
|
||||
sReadyUpBool = !sReadyUpBool;
|
||||
networking_ready_up(sReadyUpBool);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
void DrawColumn1(WidgetInfo& info) {
|
||||
if (gNetwork.isConnected) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
static char username[NETWORK_USERNAME_LENGTH] = "TestUser";
|
||||
|
||||
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
|
||||
|
||||
ImGui::Text("Host:");
|
||||
ImGui::InputText("##edit3", addressCopy, IM_ARRAYSIZE(addressCopy));
|
||||
|
||||
if (ImGui::Button("Connect!")) {
|
||||
char* ip = NULL;
|
||||
uint16_t port = 0;
|
||||
|
||||
char* token = strtok(addressCopy, ":");
|
||||
ip = token;
|
||||
|
||||
token = strtok(NULL, ":");
|
||||
if (token != NULL) {
|
||||
port = (uint16_t) atoi(token);
|
||||
}
|
||||
|
||||
gNetwork.enabled = true;
|
||||
ConnectToServer(ip, port, username);
|
||||
}
|
||||
if (gNetwork.isConnected) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerWindow::UpdateElement() {
|
||||
void DrawColumn2(WidgetInfo& info) {
|
||||
if (!gNetwork.isConnected) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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::Spacing();
|
||||
ImGui::Text("-- %s --", gCupNames[sCup]);
|
||||
|
||||
if (ImGui::Button("test", ImVec2(-1, 0))) {
|
||||
// Add disconnect logic here
|
||||
cameras[0].playerId = 2;
|
||||
D_800DC5EC->player = &gPlayers[2];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* Stick UI to the bottom of the panel */
|
||||
|
||||
float windowHeight = ImGui::GetWindowHeight();
|
||||
|
||||
// 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
|
||||
|
||||
char buttonLabel[32];
|
||||
|
||||
if (sReadyUpBool) {
|
||||
strcpy(buttonLabel, "Lets Go!");
|
||||
} else {
|
||||
strcpy(buttonLabel, "Ready Up!");
|
||||
}
|
||||
|
||||
// First button (Ready Up / Let's Go)
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1, 0))) {
|
||||
sReadyUpBool = !sReadyUpBool;
|
||||
networking_ready_up(sReadyUpBool);
|
||||
}
|
||||
|
||||
// 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 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
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];
|
||||
|
||||
void AdvancedResolutionSettingsWindow::InitElement() {
|
||||
}
|
||||
// Initialise integer scale bounds.
|
||||
static short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
|
||||
// overridden depending on viewport res
|
||||
|
||||
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.
|
||||
static short integerScale_maximumBounds = 1; // can change when window is resized
|
||||
|
||||
// Initialise integer scale bounds.
|
||||
short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
|
||||
// overridden depending on viewport res
|
||||
// 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;
|
||||
|
||||
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;
|
||||
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
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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
|
||||
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();
|
||||
});
|
||||
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
|
||||
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
|
||||
}
|
||||
} 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;
|
||||
|
||||
if (showHorizontalResField) {
|
||||
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (update[UPDATE_aspectRatioY]) {
|
||||
if (aspectRatioY < 0.0f) {
|
||||
aspectRatioY = 0.0f;
|
||||
}
|
||||
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
|
||||
}
|
||||
// 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_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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
|
||||
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount);
|
||||
CVarSave();
|
||||
}
|
||||
ImGui::End();
|
||||
// Initialise update flags.
|
||||
for (uint8_t i = 0; i < sizeof(update); i++) {
|
||||
update[i] = false;
|
||||
}
|
||||
|
||||
// Initialise integer scale bounds.
|
||||
short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
|
||||
// overridden depending on viewport res
|
||||
|
||||
short integerScale_maximumBounds = 1; // can change when window is resized
|
||||
// This is mostly just for UX purposes, as Fit Automatically logic is part of LUS.
|
||||
if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) >
|
||||
((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) {
|
||||
// Scale to window height
|
||||
integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height;
|
||||
} else {
|
||||
// Scale to window width
|
||||
integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width;
|
||||
}
|
||||
// Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS.
|
||||
// Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded.
|
||||
if (default_maxIntegerScaleFactor < integerScale_maximumBounds) {
|
||||
max_integerScaleFactor = integerScale_maximumBounds +
|
||||
CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0);
|
||||
}
|
||||
|
||||
// Combo List defaults
|
||||
item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3);
|
||||
item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount);
|
||||
// Stored Values for non-UIWidgets elements
|
||||
aspectRatioX = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]);
|
||||
aspectRatioY = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]);
|
||||
verticalPixelCount =
|
||||
CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]);
|
||||
// Additional settings
|
||||
showHorizontalResField = false;
|
||||
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
|
||||
// Disabling flags
|
||||
disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0);
|
||||
disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0);
|
||||
}
|
||||
|
||||
void AdvancedResolutionSettingsWindow::UpdateElement() {
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
#pragma once
|
||||
#ifndef RESOLUTIONEDITOR_H
|
||||
#define RESOLUTIONEDITOR_H
|
||||
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
namespace AdvancedResolutionSettings {
|
||||
class AdvancedResolutionSettingsWindow : public Ship::GuiWindow {
|
||||
private:
|
||||
bool IsDroppingFrames();
|
||||
namespace GameUI {
|
||||
bool IsDroppingFrames();
|
||||
void RegisterResolutionWidgets();
|
||||
void UpdateResolutionVars();
|
||||
} // namespace BenGui
|
||||
|
||||
protected:
|
||||
bool IsBoolArrayTrue(bool*);
|
||||
|
||||
public:
|
||||
using Ship::GuiWindow::GuiWindow;
|
||||
|
||||
void InitElement() override;
|
||||
void DrawElement() override;
|
||||
void UpdateElement() override;
|
||||
};
|
||||
} // namespace AdvancedResolutionSettings
|
||||
#endif // RESOLUTIONEDITOR_H
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
static int FilterAlphaNum(ImGuiInputTextCallbackData* data) {
|
||||
const char* alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789";
|
||||
if (data->EventChar < 256 && strchr(alphanum, (char) data->EventChar)) {
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
return 1;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Enums
|
||||
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;
|
||||
}*/
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
char* WrappedText(const char* text, unsigned int charactersPerLine = 60);
|
||||
char* WrappedText(const std::string& text, unsigned int charactersPerLine);
|
||||
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;
|
||||
}
|
||||
|
||||
void SetLastItemHoverText(const std::string& text);
|
||||
void SetLastItemHoverText(const char* text);
|
||||
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;
|
||||
}
|
||||
|
||||
void InsertHelpHoverText(const std::string& text);
|
||||
void InsertHelpHoverText(const char* text);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);
|
||||
bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic);
|
||||
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 ReEnableComponent(const char* disabledTooltipText);
|
||||
void DisableComponent(const float alpha);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
#endif /* UIWidgets_hpp */
|
||||
|
|
|
|||
Loading…
Reference in New Issue