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:
Malkierian 2025-02-04 11:43:20 -07:00 committed by GitHub
parent 301bbd3cd9
commit 695879c4cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3915 additions and 1952 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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.

3
cmake/lus-cvars.cmake Normal file
View File

@ -0,0 +1,3 @@
# kept for future reference
#set(CVAR_VSYNC_ENABLED "${CVAR_PREFIX_SETTING}.VsyncEnabled")
include("libultraship/cmake/cvars.cmake")

View File

@ -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() {

View File

@ -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

37
src/port/ShipUtils.cpp Normal file
View File

@ -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;
}

19
src/port/ShipUtils.h Normal file
View File

@ -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

View File

@ -1,10 +1,13 @@
#include "FreecamWindow.h"
#include "port/ui/PortMenu.h"
#include "UIWidgets.h"
#include "libultraship/src/Context.h"
#include <imgui.h>
#include <map>
#include <libultraship/libultraship.h>
#include "spdlog/spdlog.h"
#include <spdlog/fmt/fmt.h>
#include "spdlog/formatter.h"
#include <common_structs.h>
#include <defines.h>
#include "enhancements/freecam/freecam_engine.h"
@ -30,80 +33,166 @@ extern u32 gFreecamControllerType;
void freecam_get_player_from_character(s32 characterId);
}
namespace GameUI {
extern std::shared_ptr<PortMenu> mPortMenu;
namespace Freecam {
FreecamWindow::~FreecamWindow() {
SPDLOG_TRACE("destruct freecam window");
}
void FreecamWindow::InitElement() {
}
static s32 sReadyUpBool = false;
float dampMin = 0.970;
float dampMax = 1.0f;
float minSpeed = 1.0;
float maxSpeed = 30.0f;
float minSpeedMultiplier = 1.5f;
float maxSpeedMultiplier = 15.0f;
float minFreecamRotateFactor = 0.0f;
float maxFreecamRotateFactor = 1.0f;
float minFreecamFollowFactor = 0.0f;
float maxFreecamFollowFactor = 1.0f;
int32_t controllerType = 0;
std::unordered_map<int32_t, const char*> controlType = {{ 0, "Mouse/Keyboard" }, { 1, "Controller" }};
uint32_t focusPlayer;
void FreecamWindow::DrawElement() {
const float framerate = ImGui::GetIO().Framerate;
const float deltatime = ImGui::GetIO().DeltaTime;
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::Text("Controller mode is not configured yet.");
const char* items[] = { "Mouse/Keyboard", "Controller" };
static int current_item = 0;
if (ImGui::Combo("Dropdown", &current_item, items, IM_ARRAYSIZE(items))) {
gFreecamControllerType = current_item;
bool IsPlayerValid(const char* string) {
return string != NULL && (strncmp(string, "\xA1\xBC\xA1\xBC\xA1\xBC\xA1\xBC", 8) != 0);
}
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" });
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));
ImGui::Spacing();
//mPortMenu->AddWidget(path, "Controller mode is not configured yet.", WIDGET_TEXT);
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")) {};
//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"}}));
ImGui::Text("Target Player");
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"));
if (ImGui::Button("None")) {
fTargetPlayer = false;
mPortMenu->AddWidget(path, "Target Player", WIDGET_TEXT);
mPortMenu->AddWidget(path, "None", WIDGET_BUTTON)
.Callback([](WidgetInfo& info) { fTargetPlayer = false; });
mPortMenu->AddWidget(path, "Player 1", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[0]);
if (!info.isHidden) {
info.name = D_800E76A8[0];
}
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);
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(0);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 2", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[1]);
if (!info.isHidden) {
info.name = D_800E76A8[1];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(1);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 3", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[2]);
if (!info.isHidden) {
info.name = D_800E76A8[2];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(2);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 4", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[3]);
if (!info.isHidden) {
info.name = D_800E76A8[3];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(3);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 5", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[4]);
if (!info.isHidden) {
info.name = D_800E76A8[4];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(4);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 6", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[5]);
if (!info.isHidden) {
info.name = D_800E76A8[5];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(5);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 7", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[6]);
if (!info.isHidden) {
info.name = D_800E76A8[6];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(6);
fTargetPlayer = true;
});
mPortMenu->AddWidget(path, "Player 8", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !IsPlayerValid(D_800E76A8[7]);
if (!info.isHidden) {
info.name = D_800E76A8[7];
}
})
.Options(UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))
.SameLine(true)
.Callback([](WidgetInfo& info) {
freecam_get_player_from_character(7);
fTargetPlayer = true;
});
}
ImGui::PopStyleColor();
}
static RegisterMenuInitFunc initFunc(RegisterFreecamWidgets);
void FreecamWindow::UpdateElement() {
}
} // namespace Freecam
} // namespace GameUI

View File

@ -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

View File

@ -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();
}

801
src/port/ui/Menu.cpp Normal file
View File

@ -0,0 +1,801 @@
#include "Menu.h"
#include "UIWidgets.h"
#include "port/Engine.h"
#include "window/gui/GuiMenuBar.h"
#include "window/gui/GuiElement.h"
#include <variant>
#include <spdlog/fmt/fmt.h>
#include <variant>
#include <tuple>
extern "C" {
extern s32 gGamestateNext;
extern s32 gMenuSelection;
#include "audio/external.h"
#include "defines.h"
}
std::vector<ImVec2> windowTypeSizes = { {} };
extern std::unordered_map<s16, const char*> warpPointSceneList;
extern void Warp();
namespace BenGui {}
namespace Ship {
std::string disabledTempTooltip;
const char* disabledTooltip;
bool disabledValue = false;
bool operator==(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
return l.r == r.r && l.g == r.g && l.b == r.b;
}
bool operator==(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
return l.r == r.r && l.g == r.g && l.b == r.b && l.a == r.a;
}
bool operator<(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
return (l.r < r.r && l.g <= r.g && l.b <= r.b) || (l.r <= r.r && l.g < r.g && l.b <= r.b) ||
(l.r <= r.r && l.g <= r.g && l.b < r.b);
}
bool operator<(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
return (l.r < r.r && l.g <= r.g && l.b <= r.b && l.a <= r.a) ||
(l.r <= r.r && l.g < r.g && l.b <= r.b && l.a <= r.a) ||
(l.r <= r.r && l.g <= r.g && l.b < r.b && l.a <= r.a) ||
(l.r <= r.r && l.g <= r.g && l.b <= r.b && l.a < r.a);
}
bool operator>(Color_RGB8 const& l, Color_RGB8 const& r) noexcept {
return (l.r > r.r && l.g >= r.g && l.b >= r.b) || (l.r >= r.r && l.g > r.g && l.b >= r.b) ||
(l.r >= r.r && l.g >= r.g && l.b > r.b);
}
bool operator>(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept {
return (l.r > r.r && l.g >= r.g && l.b >= r.b && l.a >= r.a) ||
(l.r >= r.r && l.g > r.g && l.b >= r.b && l.a >= r.a) ||
(l.r >= r.r && l.g >= r.g && l.b > r.b && l.a >= r.a) ||
(l.r >= r.r && l.g >= r.g && l.b >= r.b && l.a > r.a);
}
uint32_t GetVectorIndexOf(std::vector<std::string>& vector, std::string value) {
return std::distance(vector.begin(), std::find(vector.begin(), vector.end(), value));
}
void Menu::InsertSidebarSearch() {
menuEntries["Settings"].sidebars.emplace("Search", searchSidebarEntry);
uint32_t curIndex = 0;
if (!Ship_IsCStringEmpty(CVarGetString(menuEntries["Settings"].sidebarCvar, ""))) {
curIndex = GetVectorIndexOf(menuEntries["Settings"].sidebarOrder,
CVarGetString(menuEntries["Settings"].sidebarCvar, ""));
}
menuEntries["Settings"].sidebarOrder.insert(menuEntries["Settings"].sidebarOrder.begin() + searchSidebarIndex,
"Search");
if (curIndex > searchSidebarIndex) {
CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str());
}
}
void Menu::RemoveSidebarSearch() {
uint32_t curIndex =
GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, CVarGetString(menuEntries["Settings"].sidebarCvar, ""));
menuEntries["Settings"].sidebars.erase("Search");
std::erase_if(menuEntries["Settings"].sidebarOrder, [](std::string& name) { return name == "Search"; });
if (curIndex > searchSidebarIndex) {
curIndex--;
} else if (curIndex >= menuEntries["Settings"].sidebarOrder.size()) {
curIndex = menuEntries["Settings"].sidebarOrder.size() - 1;
}
CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str());
}
void Menu::UpdateWindowBackendObjects() {
Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend();
int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1);
if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) {
configWindowBackend = static_cast<Ship::WindowBackend>(configWindowBackendId);
} else {
configWindowBackend = runningWindowBackend;
}
availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends();
for (auto& backend : *availableWindowBackends) {
availableWindowBackendsMap[backend] = windowBackendsMap.at(backend);
}
}
Menu::Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_,
UIWidgets::Colors defaultThemeIndex_)
: GuiWindow(cVar, name), searchSidebarIndex(searchSidebarIndex_), defaultThemeIndex(defaultThemeIndex_) {
}
void Menu::InitElement() {
popped = CVarGetInteger("gSettings.Menu.Popout", 0);
poppedSize.x = CVarGetInteger("gSettings.Menu.PoppedWidth", 1280);
poppedSize.y = CVarGetInteger("gSettings.Menu.PoppedHeight", 800);
poppedPos.x = CVarGetInteger("gSettings.Menu.PoppedPos.x", 0);
poppedPos.y = CVarGetInteger("gSettings.Menu.PoppedPos.y", 0);
UpdateWindowBackendObjects();
}
void Menu::UpdateElement() {
}
bool ModernMenuSidebarEntry(std::string label) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiStyle& style = ImGui::GetStyle();
ImVec2 pos = window->DC.CursorPos;
const ImGuiID sidebarId = window->GetID(std::string(label + "##Sidebar").c_str());
ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true);
pos.y += style.FramePadding.y;
pos.x = window->WorkRect.GetCenter().x - labelSize.x / 2;
ImRect bb = { pos - style.FramePadding, pos + labelSize + style.FramePadding };
ImGui::ItemSize(bb, style.FramePadding.y);
ImGui::ItemAdd(bb, sidebarId);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, sidebarId, &hovered, &held);
if (pressed) {
ImGui::MarkItemEdited(sidebarId);
}
window->DrawList->AddRectFilled(pos - style.FramePadding, pos + labelSize + style.FramePadding,
ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive
: hovered ? ImGuiCol_ButtonHovered
: ImGuiCol_Button),
3.0f);
UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true);
return pressed;
}
bool ModernMenuHeaderEntry(std::string label) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiStyle& style = ImGui::GetStyle();
ImVec2 pos = window->DC.CursorPos;
const ImGuiID headerId = window->GetID(std::string(label + "##Header").c_str());
ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true);
ImRect bb = { pos, pos + labelSize + style.FramePadding * 2 };
ImGui::ItemSize(bb, style.FramePadding.y);
ImGui::ItemAdd(bb, headerId);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, headerId, &hovered, &held);
window->DrawList->AddRectFilled(bb.Min, bb.Max,
ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive
: hovered ? ImGuiCol_ButtonHovered
: ImGuiCol_Button),
3.0f);
pos += style.FramePadding;
UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true);
return pressed;
}
uint32_t Menu::DrawSearchResults(std::string& menuSearchText) {
auto menuThemeIndex = static_cast<UIWidgets::Colors>(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex));
ImGui::BeginChild("Search Results");
int searchCount = 0;
for (auto& menuLabel : menuOrder) {
auto& menuEntry = menuEntries.at(menuLabel);
for (auto& sidebarLabel : menuEntry.sidebarOrder) {
auto& sidebar = menuEntry.sidebars[sidebarLabel];
for (int i = 0; i < sidebar.columnWidgets.size(); i++) {
auto& column = sidebar.columnWidgets.at(i);
for (auto& info : column) {
if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || info.type == WIDGET_SEPARATOR_TEXT ||
info.isHidden) {
continue;
}
const char* tooltip = info.options->tooltip;
std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : "");
std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower);
menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '),
menuSearchText.end());
std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower);
widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end());
if (widgetStr.find(menuSearchText) != std::string::npos) {
MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex);
ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray));
std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1);
ImGui::Text("%s", origin.c_str());
ImGui::PopStyleColor();
searchCount++;
}
}
}
}
}
return searchCount;
}
void Menu::AddMenuEntry(std::string entryName, const char* entryCvar) {
menuEntries.emplace(entryName, MainMenuEntry{ entryName, entryCvar });
menuOrder.push_back(entryName);
}
std::unordered_map<uint32_t, disabledInfo>& Menu::GetDisabledMap() {
return disabledMap;
}
void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex) {
disabledTempTooltip = "This setting is disabled because: \n\n";
disabledValue = false;
disabledTooltip = " ";
if (widget.preFunc != nullptr) {
widget.ResetDisables();
widget.preFunc(widget);
if (widget.isHidden) {
return;
}
if (!widget.activeDisables.empty()) {
widget.options->disabled = true;
for (auto option : widget.activeDisables) {
disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n");
}
widget.options->disabledTooltip = disabledTempTooltip.c_str();
}
}
if (widget.sameLine) {
ImGui::SameLine();
}
try {
switch (widget.type) {
case WIDGET_CHECKBOX: {
bool* pointer = std::get<bool*>(widget.valuePointer);
if (pointer == nullptr) {
SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr");
assert(false);
return;
}
auto options = std::static_pointer_cast<UIWidgets::CheckboxOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::Checkbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), pointer,
*options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_CVAR_CHECKBOX: {
auto options = std::static_pointer_cast<UIWidgets::CheckboxOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::CVarCheckbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), widget.cVar,
*options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
};
} break;
case WIDGET_AUDIO_BACKEND: {
auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend();
UIWidgets::ComboboxOptions options = {};
options.color = menuThemeIndex;
options.tooltip = "Sets the audio API used by the game. Requires a relaunch to take effect.";
options.disabled = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1;
options.disabledTooltip = "Only one audio API is available on this platform.";
if (UIWidgets::Combobox("Audio API", &currentAudioBackend, audioBackendsMap, options)) {
Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(currentAudioBackend);
}
} break;
case WIDGET_VIDEO_BACKEND: {
UIWidgets::ComboboxOptions options = {};
options.color = menuThemeIndex;
options.tooltip = "Sets the renderer API used by the game.";
options.disabled = availableWindowBackends->size() <= 1;
options.disabledTooltip = "Only one renderer API is available on this platform.";
if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap,
options)) {
Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id",
(int32_t)(configWindowBackend));
Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name",
windowBackendsMap.at(configWindowBackend));
Ship::Context::GetInstance()->GetConfig()->Save();
UpdateWindowBackendObjects();
}
} break;
case WIDGET_SEPARATOR: {
ImGui::Separator();
} break;
case WIDGET_SEPARATOR_TEXT: {
if (widget.options->color != UIWidgets::Colors::NoColor) {
ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
}
ImGui::SeparatorText(widget.name.c_str());
if (widget.options->color != UIWidgets::Colors::NoColor) {
ImGui::PopStyleColor();
}
} break;
case WIDGET_TEXT: {
if (widget.options->color != UIWidgets::Colors::NoColor) {
ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color));
}
ImGui::AlignTextToFramePadding();
ImGui::TextWrapped("%s", widget.name.c_str());
if (widget.options->color != UIWidgets::Colors::NoColor) {
ImGui::PopStyleColor();
}
} break;
case WIDGET_COMBOBOX: {
int32_t* pointer = std::get<int32_t*>(widget.valuePointer);
if (pointer == nullptr) {
SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr");
assert(false);
return;
}
auto options = std::static_pointer_cast<UIWidgets::ComboboxOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::Combobox(widget.name.c_str(), pointer, options->comboMap, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
};
} break;
case WIDGET_CVAR_COMBOBOX: {
auto options = std::static_pointer_cast<UIWidgets::ComboboxOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::CVarCombobox(widget.name.c_str(), widget.cVar, options->comboMap, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_SLIDER_INT: {
int32_t* pointer = std::get<int32_t*>(widget.valuePointer);
if (pointer == nullptr) {
SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr");
assert(false);
return;
}
auto options = std::static_pointer_cast<UIWidgets::IntSliderOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::SliderInt(widget.name.c_str(), pointer, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
};
} break;
case WIDGET_CVAR_SLIDER_INT: {
auto options = std::static_pointer_cast<UIWidgets::IntSliderOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::CVarSliderInt(widget.name.c_str(), widget.cVar, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
};
} break;
case WIDGET_SLIDER_FLOAT: {
float* pointer = std::get<float*>(widget.valuePointer);
if (pointer == nullptr) {
SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr");
assert(false);
return;
}
auto options = std::static_pointer_cast<UIWidgets::FloatSliderOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::SliderFloat(widget.name.c_str(), pointer, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_CVAR_SLIDER_FLOAT: {
auto options = std::static_pointer_cast<UIWidgets::FloatSliderOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::CVarSliderFloat(widget.name.c_str(), widget.cVar, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_BUTTON: {
auto options = std::static_pointer_cast<UIWidgets::ButtonOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::Button(widget.name.c_str(), *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_CUSTOM: {
if (widget.customFunction != nullptr) {
widget.customFunction(widget);
}
} break;
case WIDGET_WINDOW_BUTTON: {
if (widget.windowName == nullptr || widget.windowName[0] == '\0') {
std::string msg =
fmt::format("Error drawing window contents for {}: windowName not defined", widget.name);
SPDLOG_ERROR(msg.c_str());
break;
}
auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.windowName);
if (!window) {
std::string msg =
fmt::format("Error drawing window contents: windowName {} does not exist", widget.windowName);
SPDLOG_ERROR(msg.c_str());
break;
}
auto options = std::static_pointer_cast<UIWidgets::ButtonOptions>(widget.options);
options->color = menuThemeIndex;
UIWidgets::WindowButton(widget.name.c_str(), widget.cVar, window, *options);
if (!window->IsVisible()) {
window->DrawElement();
}
} break;
case WIDGET_SEARCH: {
UIWidgets::PushStyleButton(menuThemeIndex);
if (ImGui::Button("Clear")) {
menuSearch.Clear();
}
ImGui::SameLine();
if (CVarGetInteger("gSettings.Menu.SearchAutofocus", 0) &&
ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
!ImGui::IsMouseClicked(0)) {
ImGui::SetKeyboardFocusHere(0);
}
UIWidgets::PushStyleCombobox(menuThemeIndex);
ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(menuThemeIndex));
menuSearch.Draw();
ImGui::PopStyleColor();
UIWidgets::PopStyleCombobox();
UIWidgets::PopStyleButton();
std::string menuSearchText(menuSearch.InputBuf);
if (menuSearchText == "") {
ImGui::Text("Start typing to see results.");
return;
}
DrawSearchResults(menuSearchText);
ImGui::EndChild();
} break;
default:
break;
}
if (widget.postFunc != nullptr) {
widget.postFunc(widget);
}
} catch (const std::bad_variant_access& e) {
SPDLOG_ERROR("Failed to draw menu item \"{}\" due to: {}", widget.name, e.what());
assert(false);
}
}
void Menu::Draw() {
if (!IsVisible()) {
return;
}
DrawElement();
// Sync up the IsVisible flag if it was changed by ImGui
SyncVisibilityConsoleVariable();
}
void Menu::DrawElement() {
for (auto& [reason, info] : disabledMap) {
info.active = info.evaluation(info);
}
auto menuThemeIndex = static_cast<UIWidgets::Colors>(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex));
windowHeight = ImGui::GetMainViewport()->WorkSize.y;
windowWidth = ImGui::GetMainViewport()->WorkSize.x;
auto windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings;
bool popout = CVarGetInteger("gSettings.Menu.Popout", 0) && allowPopout;
if (popout) {
windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking;
}
if (popout != popped) {
if (popout) {
windowHeight = poppedSize.y;
windowWidth = poppedSize.x;
ImGui::SetNextWindowSize({ static_cast<float>(windowWidth), static_cast<float>(windowHeight) },
ImGuiCond_Always);
ImGui::SetNextWindowPos(poppedPos, ImGuiCond_Always);
} else if (popped) {
CVarSetFloat("gSettings.Menu.PoppedWidth", poppedSize.x);
CVarSetFloat("gSettings.Menu.PoppedHeight", poppedSize.y);
CVarSave();
}
}
popped = popout;
auto windowCond = ImGuiCond_Always;
if (!popout) {
ImGui::SetNextWindowSize({ static_cast<float>(windowWidth), static_cast<float>(windowHeight) }, windowCond);
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), windowCond, { 0.5f, 0.5f });
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
}
if (!ImGui::Begin("Main Menu", NULL, windowFlags)) {
if (!popout) {
ImGui::PopStyleVar();
}
ImGui::End();
return;
}
if (popped != popout) {
if (!popout) {
ImGui::PopStyleVar();
}
CVarSetInteger("gSettings.Menu.Popout", popped);
CVarSetFloat("gSettings.Menu.PoppedWidth", poppedSize.x);
CVarSetFloat("gSettings.Menu.PoppedHeight", poppedSize.y);
CVarSetFloat("gSettings.Menu.PoppedPos.x", poppedSize.x);
CVarSetFloat("gSettings.Menu.PoppedPos.y", poppedSize.y);
CVarSave();
ImGui::End();
return;
}
ImGui::PushFont(GameEngine::Instance->fontStandardLargest);
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiStyle& style = ImGui::GetStyle();
windowHeight = window->WorkRect.GetHeight();
windowWidth = window->WorkRect.GetWidth();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f));
const char* headerCvar = "gSettings.Menu.ActiveHeader";
std::string headerIndex = CVarGetString(headerCvar, "Settings");
ImVec2 pos = window->DC.CursorPos;
float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1));
std::vector<ImVec2> headerSizes;
float headerWidth = style.ItemSpacing.x;
bool headerSearch = !CVarGetInteger("gSettings.Menu.SidebarSearch", 0);
if (headerSearch) {
headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x;
}
for (auto& label : menuOrder) {
ImVec2 size = ImGui::CalcTextSize(label.c_str());
headerSizes.push_back(size);
headerWidth += size.x + style.FramePadding.x * 2;
if (label == headerIndex) {
headerWidth += style.ItemSpacing.x;
}
}
ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) };
pos += window->WorkRect.GetSize() / 2 - menuSize / 2;
ImGui::SetNextWindowPos(pos);
ImGui::BeginChild("Menu Block", menuSize,
ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar);
std::unordered_map<std::string, SidebarEntry>* sidebar;
float headerHeight = headerSizes.at(0).y + style.FramePadding.y * 2;
ImVec2 buttonSize = ImGui::CalcTextSize(ICON_FA_TIMES_CIRCLE) + style.FramePadding * 2;
bool scrollbar = false;
if (headerWidth > menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3) {
headerHeight += style.ScrollbarSize;
scrollbar = true;
}
UIWidgets::ButtonOptions options = {};
options.size = UIWidgets::Sizes::Inline;
options.tooltip = "Close Menu (Esc)";
if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) {
ToggleVisibility();
// Update gamepad navigation after close based on if other menus are still visible
auto mImGuiIo = &ImGui::GetIO();
if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) &&
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) {
mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
} else {
mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
}
}
ImGui::SameLine();
ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight });
ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight };
if (scrollbar) {
headerSelSize.y += style.ScrollbarSize;
}
bool autoFocus = CVarGetInteger("gSettings.Menu.SearchAutofocus", 0);
ImGui::BeginChild("Header Selection", headerSelSize,
ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar);
uint8_t curIndex = 0;
for (auto& label : menuOrder) {
if (curIndex != 0) {
ImGui::SameLine();
}
auto& entry = menuEntries.at(label);
std::string nextIndex = label;
UIWidgets::PushStyleButton(menuThemeIndex);
if (headerIndex != label) {
ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
}
if (ModernMenuHeaderEntry(entry.label)) {
if (headerSearch) {
menuSearch.Clear();
}
CVarSetString(headerCvar, label.c_str());
CVarSave();
nextIndex = label;
}
if (headerIndex != label) {
ImGui::PopStyleColor();
}
UIWidgets::PopStyleButton();
if (headerIndex == label) {
sidebar = &entry.sidebars;
}
if (nextIndex != label) {
headerIndex = nextIndex;
}
curIndex++;
}
std::string menuSearchText = "";
if (headerSearch) {
ImGui::SameLine();
if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
!ImGui::IsMouseClicked(0)) {
ImGui::SetKeyboardFocusHere(0);
}
auto color = UIWidgets::ColorValues.at(menuThemeIndex);
color.w = 0.2f;
ImGui::PushStyleColor(ImGuiCol_FrameBg, color);
menuSearch.Draw("##search", 200.0f);
menuSearchText = menuSearch.InputBuf;
menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), menuSearchText.end());
if (menuSearchText.length() < 1) {
ImGui::SameLine(headerWidth - 200.0f + style.ItemSpacing.x);
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search...");
}
ImGui::PopStyleColor();
}
ImGui::EndChild();
ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x);
UIWidgets::ButtonOptions options2 = {};
options2.color = UIWidgets::Colors::Red;
options2.size = UIWidgets::Sizes::Inline;
options2.tooltip = "Reset"
#ifdef __APPLE__
" (Command-R)"
#elif !defined(__SWITCH__) && !defined(__WIIU__)
" (Ctrl+R)"
#else
""
#endif
;
if (UIWidgets::Button(ICON_FA_UNDO, options2)) {
ProcessReset();
}
ImGui::SameLine();
UIWidgets::ButtonOptions options3 = {};
options3.color = UIWidgets::Colors::Red;
options3.size = UIWidgets::Sizes::Inline;
options3.tooltip = "Quit 2S2H";
if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) {
if (!popped) {
ToggleVisibility();
}
Ship::Context::GetInstance()->GetWindow()->Close();
}
ImGui::PopStyleVar();
pos.y += headerHeight + style.ItemSpacing.y;
pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1));
window->DrawList->AddRectFilled(pos, pos + ImVec2{ menuSize.x, 4 }, ImGui::GetColorU32({ 255, 255, 255, 255 }),
true, style.WindowRounding);
pos.y += style.ItemSpacing.y;
float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2;
float columnHeight = sectionHeight - style.ItemSpacing.y * 4;
ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2);
float sidebarWidth = 200 - style.ItemSpacing.x;
const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar;
std::string sectionIndex = CVarGetString(sidebarCvar, "");
if (!sidebar->contains(sectionIndex)) {
sectionIndex = sidebar->begin()->first;
}
float sectionCenterX = pos.x + (sidebarWidth / 2);
float topY = pos.y;
ImGui::SetNextWindowSizeConstraints({ sidebarWidth, 0 }, { sidebarWidth, columnHeight });
ImGui::BeginChild((menuEntries.at(headerIndex).label + " Section").c_str(), { sidebarWidth, columnHeight * 3 },
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoTitleBar);
for (auto& sidebarLabel : menuEntries.at(headerIndex).sidebarOrder) {
std::string nextIndex = "";
UIWidgets::PushStyleButton(menuThemeIndex);
if (sectionIndex != sidebarLabel) {
ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
}
if (ModernMenuSidebarEntry(sidebarLabel)) {
if (headerSearch) {
menuSearch.Clear();
}
CVarSetString(sidebarCvar, sidebarLabel.c_str());
CVarSave();
nextIndex = sidebarLabel;
}
if (sectionIndex != sidebarLabel) {
ImGui::PopStyleColor();
}
UIWidgets::PopStyleButton();
if (nextIndex != "") {
sectionIndex = nextIndex;
}
}
ImGui::EndChild();
ImGui::PushFont(GameEngine::Instance->fontMonoLarger);
pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2;
window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 },
ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding);
pos.x += 4 + style.ItemSpacing.x;
ImGui::SetNextWindowPos(pos + style.ItemSpacing);
float sectionWidth = menuSize.x - sidebarWidth - 4 - style.ItemSpacing.x * 4;
std::string sectionMenuId = sectionIndex + " Settings";
int columns = sidebar->at(sectionIndex).columnCount;
size_t columnFuncs = sidebar->at(sectionIndex).columnWidgets.size();
if (windowWidth < 800) {
columns = 1;
}
float columnWidth = (sectionWidth - style.ItemSpacing.x * columns) / columns;
bool useColumns = columns > 1;
if (!useColumns || (headerSearch && menuSearchText.length() > 0)) {
ImGui::SameLine();
ImGui::SetNextWindowSizeConstraints({ sectionWidth, 0 }, { sectionWidth, columnHeight });
ImGui::BeginChild(sectionMenuId.c_str(), { sectionWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY,
ImGuiWindowFlags_NoTitleBar);
}
if (headerSearch && menuSearchText.length() > 0) {
uint32_t searchCount = DrawSearchResults(menuSearchText);
if (searchCount == 0) {
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No results found").x) / 2);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f);
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "No results found");
}
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Clear Search").x) / 2 - 10.0f);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f);
UIWidgets::ButtonOptions clearBtnOpts = {};
clearBtnOpts.size = UIWidgets::Sizes::Inline;
if (UIWidgets::Button("Clear Search", clearBtnOpts)) {
menuSearch.Clear();
}
ImGui::EndChild();
} else {
std::string menuLabel = menuEntries.at(headerIndex).label;
if (MenuInit::GetUpdateFuncs().contains(menuLabel)) {
if (MenuInit::GetUpdateFuncs()[menuLabel].contains(sectionIndex)) {
for (auto& updateFunc : MenuInit::GetUpdateFuncs()[menuLabel][sectionIndex]) {
updateFunc();
}
}
}
for (int i = 0; i < columnFuncs; i++) {
std::string sectionId = fmt::format("{} Column {}", sectionMenuId, i);
if (useColumns) {
ImGui::SetNextWindowSizeConstraints({ columnWidth, 0 }, { columnWidth, columnHeight });
ImGui::BeginChild(sectionId.c_str(), { columnWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY,
ImGuiWindowFlags_NoTitleBar);
}
// for (auto& entryName : sidebar->at(sectionIndex).sidebarOrder) {
for (auto& entry : sidebar->at(sectionIndex).columnWidgets.at(i)) {
MenuDrawItem(entry, 90 / sidebar->at(sectionIndex).columnCount, menuThemeIndex);
}
//}
if (useColumns) {
ImGui::EndChild();
}
if (i < columns - 1) {
ImGui::SameLine();
}
}
}
if (!useColumns || menuSearchText.length() > 0) {
ImGui::EndChild();
}
ImGui::PopFont();
ImGui::PopFont();
if (!popout) {
ImGui::PopStyleVar();
}
ImGui::EndChild();
if (popout) {
poppedSize = ImGui::GetWindowSize();
poppedPos = ImGui::GetWindowPos();
}
ImGui::End();
}
} // namespace Ship

66
src/port/ui/Menu.h Normal file
View File

@ -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

303
src/port/ui/MenuTypes.h Normal file
View File

@ -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

View File

@ -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,25 +22,19 @@ 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) {
void DrawColumn1(WidgetInfo& info) {
if (gNetwork.isConnected) {
ImGui::BeginDisabled();
}
static char username[NETWORK_USERNAME_LENGTH] = "TestUser";
@ -69,10 +64,16 @@ void MultiplayerWindow::DrawElement() {
gNetwork.enabled = true;
ConnectToServer(ip, port, username);
}
if (gNetwork.isConnected) {
ImGui::EndDisabled();
}
}
} else {
void DrawColumn2(WidgetInfo& info) {
if (!gNetwork.isConnected) {
ImGui::BeginDisabled();
}
ImGui::Spacing();
ImGui::Text("Select Character:");
ImGui::Spacing();
@ -145,11 +146,21 @@ void MultiplayerWindow::DrawElement() {
// Add disconnect logic here
networking_disconnect();
}
if (!gNetwork.isConnected) {
ImGui::EndDisabled();
}
}
ImGui::PopStyleColor();
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);
}
void MultiplayerWindow::UpdateElement() {
}
static RegisterMenuInitFunc initFunc(RegisterMultiplayerWidgets);
} // namespace Multiplayer
} // namespace GameUI

View File

@ -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

497
src/port/ui/PortMenu.cpp Normal file
View File

@ -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

64
src/port/ui/PortMenu.h Normal file
View File

@ -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

View File

@ -1,65 +1,495 @@
#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
static short integerScale_maximumBounds = 1; // can change when window is resized
// Combo List defaults
static int32_t item_aspectRatio;
static int32_t item_pixelCount;
// Stored Values for non-UIWidgets elements
static float aspectRatioX;
static float aspectRatioY;
static int32_t verticalPixelCount;
// Additional settings
static bool showHorizontalResField;
static int32_t horizontalPixelCount;
// Disabling flags
static bool disabled_everything;
static bool disabled_pixelCount;
using namespace UIWidgets;
void RegisterResolutionWidgets() {
WidgetPath path = { "Settings", "Graphics", SECTION_COLUMN_2 };
#ifdef __APPLE__
mPortMenu
->AddWidget(path, ICON_FA_INFO_CIRCLE " These settings may behave incorrectly on Retina displays.", WIDGET_TEXT)
.Options(WidgetOptions().Color(Colors::Green));
#endif
// Resolution visualiser
mPortMenu->AddWidget(path, "Viewport dimensions: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
info.name = fmt::format("Viewport dimensions: {} x {}", gfx_current_game_window_viewport.width,
gfx_current_game_window_viewport.height);
});
mPortMenu->AddWidget(path, "Internal resolution: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
info.name =
fmt::format("Internal resolution: {} x {}", gfx_current_dimensions.width, gfx_current_dimensions.height);
});
// UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Activator
mPortMenu->AddWidget(path, "Enable advanced settings.", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled");
/*UIWidgets::PaddedEnhancementCheckbox(, , false, false,
false, "", UIWidgets::CheckboxGraphics::Cross, false);*/
// Error/Warning display
mPortMenu
->AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.",
WIDGET_TEXT)
.PreFunc(
[](WidgetInfo& info) { info.isHidden = !(!CVarGetInteger(CVAR_LOW_RES_MODE, 0) && IsDroppingFrames()); })
.Options(WidgetOptions().Color(Colors::Orange));
mPortMenu->AddWidget(path, ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.", WIDGET_TEXT)
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); })
.Options(WidgetOptions().Color(Colors::LightBlue));
mPortMenu->AddWidget(path, "Click to disable N64 mode", WIDGET_BUTTON)
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); })
.Callback([](WidgetInfo& info) {
CVarSetInteger(CVAR_LOW_RES_MODE, 0);
CVarSave();
});
// if (disabled_everything) { // Hide aspect ratio controls.
// UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
// }
// Aspect Ratio
mPortMenu->AddWidget(path, "AspectSep", WIDGET_SEPARATOR);
mPortMenu->AddWidget(path, "Force aspect ratio:", WIDGET_TEXT);
mPortMenu->AddWidget(path, "(Select \"Off\" to disable.)", WIDGET_TEXT)
.SameLine(true)
.Options(WidgetOptions().Color(Colors::Gray));
// Presets
mPortMenu->AddWidget(path, "Aspect Ratio", WIDGET_COMBOBOX)
.ValuePointer(&item_aspectRatio)
.PreFunc([](WidgetInfo& info) {
info.isHidden = !mPortMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active;
})
.Callback([](WidgetInfo& info) {
if (item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected.
aspectRatioX = aspectRatioPresetsX[item_aspectRatio];
aspectRatioY = aspectRatioPresetsY[item_aspectRatio];
if (showHorizontalResField) {
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
void AdvancedResolutionSettingsWindow::DrawElement() {
ImGui::SetNextWindowSize(ImVec2(497, 532), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Advanced Resolution Settings", &mIsVisible)) {
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
}
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
CVarSave();
})
.Options(ComboboxOptions().ComboMap(aspectRatioPresetLabels));
mPortMenu->AddWidget(path, "AspectRationCustom", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
// Hide aspect ratio input fields if using one of the presets.
if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) {
// 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) {
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();
// }
}
void UpdateResolutionVars() {
// Clamp and update the cvars that don't use UIWidgets
if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) {
if (update[UPDATE_aspectRatioX]) {
if (aspectRatioX < 0.0f) {
aspectRatioX = 0.0f;
}
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX);
}
if (update[UPDATE_aspectRatioY]) {
if (aspectRatioY < 0.0f) {
aspectRatioY = 0.0f;
}
CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY);
}
if (update[UPDATE_verticalPixelCount]) {
// There's a upper and lower clamp on the Libultraship side too,
// so clamping it here is entirely visual, so the vertical resolution field reflects it.
if (verticalPixelCount < minVerticalPixelCount) {
verticalPixelCount = minVerticalPixelCount;
}
if (verticalPixelCount > maxVerticalPixelCount) {
verticalPixelCount = maxVerticalPixelCount;
}
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount);
}
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio);
CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount);
CVarSave();
}
// Initialise update flags.
bool update[sizeof(setting)];
for (unsigned short i = 0; i < sizeof(setting); i++)
for (uint8_t i = 0; i < sizeof(update); i++) {
update[i] = false;
static short updateCountdown = 0;
short countdownStartingValue = CVarGetInteger("gInterpolationFPS", 30) / 2; // half of a second, in frames.
}
// Initialise integer scale bounds.
short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
@ -78,345 +508,35 @@ void AdvancedResolutionSettingsWindow::DrawElement() {
// 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);
max_integerScaleFactor = integerScale_maximumBounds +
CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".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;
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
static bool showHorizontalResField = false;
static int horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
#ifdef __APPLE__
ImGui::Text("Note: these settings may behave incorrectly on Apple Retina Displays.");
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
#endif
// The original resolution slider (for convenience)
if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f, 2.0f,
"", 1.0f, true, true,
(CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0) &&
CVarGetInteger("gAdvancedResolution.Enabled", 0)) ||
CVarGetInteger("gLowResMode", 0))) {
Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1));
}
UIWidgets::Tooltip("Multiplies your output resolution by the value entered, as a more intensive but effective "
"form of anti-aliasing"); // Description pulled from SohMenuBar.cpp
// N64 Mode toggle (again for convenience)
// UIWidgets::PaddedEnhancementCheckbox("(Enhancements>Graphics) N64 Mode", "gLowResMode", false, false, false,
// "", UIWidgets::CheckboxGraphics::Cross, false);
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Activator
UIWidgets::PaddedEnhancementCheckbox("Enable advanced settings.", "gAdvancedResolution.Enabled", false, false,
false, "", UIWidgets::CheckboxGraphics::Cross, false);
// Error/Warning display
if (!CVarGetInteger("gLowResMode", 0)) {
if (IsDroppingFrames()) { // Significant frame drop warning
ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f },
ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.");
UIWidgets::Spacer(2);
} else { // No warnings
UIWidgets::Spacer(enhancementSpacerHeight);
}
} else { // N64 Mode warning
ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.");
ImGui::SameLine();
if (ImGui::Button("Click to disable")) {
CVarSetInteger("gLowResMode", (int) false);
CVarSave();
}
}
// Resolution visualiser
ImGui::Text("Viewport dimensions: %d x %d", gfx_current_game_window_viewport.width,
gfx_current_game_window_viewport.height);
ImGui::Text("Internal resolution: %d x %d", gfx_current_dimensions.width, gfx_current_dimensions.height);
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Aspect Ratio
ImGui::Text("Force aspect ratio:");
ImGui::SameLine();
ImGui::TextColored({ 0.75f, 0.75f, 0.75f, 1.0f }, "(Select \"Off\" to disable.)");
if (ImGui::Combo(" ", &item_aspectRatio, aspectRatioPresetLabels,
IM_ARRAYSIZE(aspectRatioPresetLabels)) &&
item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected.
aspectRatioX = aspectRatioPresetsX[item_aspectRatio];
aspectRatioY = aspectRatioPresetsY[item_aspectRatio];
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
if (showHorizontalResField) {
showHorizontalResField = false;
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
}
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) {
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);
}
// Disabling flags
disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0);
disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0);
}
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;
}
}
// Horizontal Resolution, if visibility is enabled for it.
if (showHorizontalResField) {
// Only show the field if Aspect Ratio is being enforced.
if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) {
// So basically we're "faking" this one by setting aspectRatioX instead.
if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) {
item_aspectRatio = default_aspectRatio;
if (horizontalPixelCount < (minVerticalPixelCount / 3.0f) * 4.0f) {
horizontalPixelCount = (minVerticalPixelCount / 3.0f) * 4.0f;
}
aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
update[UPDATE_aspectRatioX] = true;
}
} else { // Display a notice instead.
ImGui::TextColored({ 0.0f, 0.85f, 0.85f, 1.0f },
ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required.");
// ImGui::Text(" ");
ImGui::SameLine();
if (ImGui::Button("Click to resolve")) {
item_aspectRatio = default_aspectRatio; // Set it to Custom
aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults
aspectRatioY = aspectRatioPresetsY[2];
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
}
}
// Vertical Resolution part 2
if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) {
item_pixelCount = default_pixelCount;
update[UPDATE_verticalPixelCount] = true;
// Account for the natural instinct to enter horizontal first.
// Ignore vertical resolutions that are below the lower clamp constant.
if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) {
aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
update[UPDATE_aspectRatioX] = true;
}
}
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();
}
}
ImGui::End();
}
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

View File

@ -1,19 +1,12 @@
#pragma once
#ifndef RESOLUTIONEDITOR_H
#define RESOLUTIONEDITOR_H
#include <libultraship/libultraship.h>
namespace AdvancedResolutionSettings {
class AdvancedResolutionSettingsWindow : public Ship::GuiWindow {
private:
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

View File

@ -1,15 +1,20 @@
#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 {
using SectionFunc = void(*)();
struct TextFilters {
static int FilterNumbers(ImGuiInputTextCallbackData* data) {
if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) {
@ -27,103 +32,64 @@ struct TextFilters {
}
};
// MARK: - Enums
enum class CheckboxGraphics { Cross, Checkmark, None };
constexpr float maxSliderWidth = 260.0f;
#ifdef __SWITCH__
constexpr float sliderButtonWidth = 42.0f;
#elif defined(__WIIU__)
constexpr float sliderButtonWidth = 60.0f;
#else
constexpr float sliderButtonWidth = 30.0f;
#endif
char* WrappedText(const char* text, unsigned int charactersPerLine = 60);
char* WrappedText(const std::string& text, unsigned int charactersPerLine);
void SetLastItemHoverText(const std::string& text);
void SetLastItemHoverText(const char* text);
void InsertHelpHoverText(const std::string& text);
void InsertHelpHoverText(const char* text);
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);
void Spacer(float height);
void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f,
float extraVerticalBottomPadding = 0.0f);
void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);
bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic);
// 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
};
void ReEnableComponent(const char* disabledTooltipText);
void DisableComponent(const float alpha);
bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false,
const char* disabledTooltipText = "",
CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false);
bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true,
bool disabled = false, const char* disabledTooltipText = "",
CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false);
bool EnhancementCombobox(const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray,
uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "",
uint8_t disabledValue = -1);
bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName,
std::span<const char*, std::dynamic_extent> comboArray,
uint8_t defaultIndex, bool disabled = false,
const char* disabledTooltipText = "", uint8_t disabledValue = -1);
void PaddedText(const char* text, bool padTop = true, bool padBottom = true);
bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format,
int defaultValue = 0, bool PlusMinusButton = true, bool disabled = false,
const char* disabledTooltipText = "");
bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max,
const char* format, int defaultValue = 0, bool PlusMinusButton = true,
bool padTop = true, bool padBottom = true, bool disabled = false,
const char* disabledTooltipText = "");
bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true,
bool disabled = false, const char* disabledTooltipText = "");
bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max,
const char* format, float defaultValue, bool isPercentage,
bool PlusMinusButton = true, bool padTop = true, bool padBottom = true,
bool disabled = false, const char* disabledTooltipText = "");
bool EnhancementRadioButton(const char* text, const char* cvarName, int id);
bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha);
bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors);
void DrawLockColorCheckbox(const char* cvarName);
void RainbowColor(const char* cvarName, ImVec4* colors);
void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha);
bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors,
bool allow_rainbow = true, bool has_alpha = false, bool TitleSameLine = false);
void DrawFlagArray32(const std::string& name, uint32_t& flags);
void DrawFlagArray16(const std::string& name, uint16_t& flags);
void DrawFlagArray8(const std::string& name, uint8_t& flags);
// V2
namespace Colors {
const ImVec4 White = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
const ImVec4 Gray = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
const ImVec4 DarkGray = ImVec4(0.1f, 0.1f, 0.1f, 1.0f);
const ImVec4 Indigo = ImVec4(0.24f, 0.31f, 0.71f, 1.0f);
const ImVec4 Red = ImVec4(0.5f, 0.0f, 0.0f, 1.0f);
const ImVec4 DarkRed = ImVec4(0.3f, 0.0f, 0.0f, 1.0f);
const ImVec4 LightGreen = ImVec4(0.0f, 0.7f, 0.0f, 1.0f);
const ImVec4 Green = ImVec4(0.0f, 0.5f, 0.0f, 1.0f);
const ImVec4 DarkGreen = ImVec4(0.0f, 0.3f, 0.0f, 1.0f);
const ImVec4 Yellow = ImVec4(1.0f, 0.627f, 0.0f, 1.0f);
}; // namespace Colors
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);
} // namespace Sizes
}
enum LabelPosition {
Near,
@ -138,94 +104,608 @@ enum ComponentAlignment {
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;
struct WidgetOptions{
const char* tooltip = "";
bool disabled = false;
const char* disabledTooltip = "";
Colors color = Colors::NoColor;
WidgetOptions& Color(Colors color_) {
color = color = color_;
return *this;
}
WidgetOptions& Tooltip(const char* tooltip_) {
tooltip = tooltip_;
return *this;
}
WidgetOptions& Disabled(bool disabled_) {
disabled = disabled_;
return *this;
}
};
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 ButtonOptions : WidgetOptions {
ImVec2 size = Sizes::Fill;
Colors color = Colors::Gray;
struct CheckboxOptions {
const ImVec4 color = Colors::Indigo;
const char* tooltip = "";
bool disabled = false;
const char* disabledTooltip = "";
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;
}
};
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 = "";
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;
}
};
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 = "";
struct IntSliderOptions : WidgetOptions {
bool showButtons = true;
ImGuiSliderFlags flags = 0;
const char* format = "%d";
const uint32_t step = 1;
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 {
const ImVec4 color = Colors::Gray;
const char* tooltip = "";
bool disabled = false;
const char* disabledTooltip = "";
struct FloatSliderOptions : WidgetOptions {
bool showButtons = true;
ImGuiSliderFlags flags = 0;
const char* format = "%f";
const float step = 0.01f;
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 PushStyleSlider(const ImVec4& color = Colors::Indigo);
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;
}
bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) {
bool dirty = false;
int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
if (Combobox(label, &value, comboSource, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
ShipInit::Init(cvarName);
dirty = true;
}
return dirty;
}*/
template <typename T>
bool Combobox(const char* label, T* value, const std::unordered_map<T, const char*>& comboMap, const ComboboxOptions& options = {}) {
bool dirty = false;
float startX = ImGui::GetCursorPosX();
std::string invisibleLabelStr = "##" + std::string(label);
const char* invisibleLabel = invisibleLabelStr.c_str();
ImGui::PushID(label);
ImGui::BeginGroup();
ImGui::BeginDisabled(options.disabled);
PushStyleCombobox(options.color);
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::Text("%s", label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::NewLine();
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text("%s", label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4;
ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
}
}
if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
for (const auto& pair : comboMap) {
if (strlen(pair.second) > 1) {
if (ImGui::Selectable(pair.second, pair.first == *value)) {
*value = pair.first;
dirty = true;
}
}
}
ImGui::PopStyleVar();
ImGui::EndCombo();
}
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine();
ImGui::Text("%s", label);
} else if (options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text("%s", label);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(startX);
ImGui::Text("%s", label);
}
}
PopStyleCombobox();
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
return dirty;
}
template <typename T = size_t>
bool Combobox(const char* label, T* value, const std::vector<const char*>& comboVector, const ComboboxOptions& options = {}) {
bool dirty = false;
float startX = ImGui::GetCursorPosX();
size_t currentValueIndex = static_cast<size_t>(*value);
std::string invisibleLabelStr = "##" + std::string(label);
const char* invisibleLabel = invisibleLabelStr.c_str();
ImGui::PushID(label);
ImGui::BeginGroup();
ImGui::BeginDisabled(options.disabled);
PushStyleCombobox(options.color);
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::Text(label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::NewLine();
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text(label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
float width = ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4;
ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
}
}
if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex), options.flags)) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
for (size_t i = 0; i < comboVector.size(); ++i) {
auto newValue = static_cast<T>(i);
if (strlen(comboVector.at(i)) > 1) {
if (ImGui::Selectable(comboVector.at(i), newValue == *value)) {
*value = newValue;
dirty = true;
}
}
}
ImGui::PopStyleVar();
ImGui::EndCombo();
}
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine();
ImGui::Text(label);
} else if (options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text(label);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(startX);
ImGui::Text(label);
}
}
PopStyleCombobox();
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
return dirty;
}
template <typename T = size_t, size_t N>
bool Combobox(const char* label, T* value, const char* (&comboArray)[N], const ComboboxOptions& options = {}) {
bool dirty = false;
float startX = ImGui::GetCursorPosX();
size_t currentValueIndex = static_cast<size_t>(*value);
if (currentValueIndex >= N) {
currentValueIndex = 0;
}
std::string invisibleLabelStr = "##" + std::string(label);
const char* invisibleLabel = invisibleLabelStr.c_str();
ImGui::PushID(label);
ImGui::BeginGroup();
ImGui::BeginDisabled(options.disabled);
PushStyleCombobox(options.color);
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::Text("%s", label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Above) {
ImGui::NewLine();
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text("%s", label);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
} else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) {
float width = ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4;
ImGui::SameLine(ImGui::GetContentRegionAvail().x - width);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
}
}
if (ImGui::BeginCombo(invisibleLabel, comboArray[currentValueIndex], options.flags)) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f));
for (size_t i = 0; i < N; ++i) {
auto newValue = static_cast<T>(i);
if (strlen(comboArray[i]) > 1) {
if (ImGui::Selectable(comboArray[i], newValue == *value)) {
*value = newValue;
dirty = true;
}
}
}
ImGui::PopStyleVar();
ImGui::EndCombo();
}
if (options.alignment == ComponentAlignment::Left) {
if (options.labelPosition == LabelPosition::Near) {
ImGui::SameLine();
ImGui::Text("%s", label);
} else if (options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x);
ImGui::Text("%s", label);
}
} else if (options.alignment == ComponentAlignment::Right) {
if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) {
ImGui::SameLine(startX);
ImGui::Text("%s", label);
}
}
PopStyleCombobox();
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
return dirty;
}
template <typename T = int32_t>
bool CVarCombobox(const char* label, const char* cvarName, const std::unordered_map<T, const char*>& comboMap, const ComboboxOptions& options = {}) {
bool dirty = false;
int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
if (Combobox<T>(label, &value, comboMap, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
dirty = true;
}
return dirty;
}
template <typename T = int32_t>
bool CVarCombobox(const char* label, const char* cvarName, const std::vector<const char*>& comboVector, const ComboboxOptions& options = {}) {
bool dirty = false;
int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
if (Combobox<T>(label, &value, comboVector, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
dirty = true;
}
return dirty;
}
template <typename T = int32_t, size_t N>
bool CVarCombobox(const char* label, const char* cvarName, const char* (&comboArray)[N], const ComboboxOptions& options = {}) {
bool dirty = false;
int32_t value = CVarGetInteger(cvarName, options.defaultIndex);
if (Combobox<T>(label, &value, comboArray, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
dirty = true;
}
return dirty;
}
void PushStyleSlider(Colors color = Colors::LightBlue);
void PopStyleSlider();
bool SliderInt(const char* label, int32_t* value, 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
bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options = {});
bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options = {});
bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options = {});
bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options = {});
bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor);
void DrawFlagArray32(const std::string& name, uint32_t& flags);
void DrawFlagArray16(const std::string& name, uint16_t& flags);
void DrawFlagArray8(const std::string& name, uint8_t& flags);
void DrawFlagArray8Mask(const std::string& name, uint8_t& flags);
}
#endif /* UIWidgets_hpp */