#include "SohMenu.h" #include "soh/Notification/Notification.h" #include "soh/Enhancements/enhancementTypes.h" #include "SohModals.h" #include "soh/OTRGlobals.h" #include #include "soh/ResourceManagerHelpers.h" #include "UIWidgets.hpp" #include extern "C" { #include "include/z64audio.h" #include "variables.h" } namespace SohGui { extern std::shared_ptr mSohMenu; extern std::shared_ptr mModalWindow; using namespace UIWidgets; static std::map imguiScaleOptions = { { 0, "Small" }, { 1, "Normal" }, { 2, "Large" }, { 3, "X-Large" }, }; static const std::map 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::map textureFilteringMap = { { Fast::FILTER_THREE_POINT, "Three-Point" }, { Fast::FILTER_LINEAR, "Linear" }, { Fast::FILTER_NONE, "None" }, }; static const std::map notificationPosition = { { 0, "Top Left" }, { 1, "Top Right" }, { 2, "Bottom Left" }, { 3, "Bottom Right" }, { 4, "Hidden" }, }; static const std::map bootSequenceLabels = { { BOOTSEQUENCE_DEFAULT, "Default" }, { BOOTSEQUENCE_AUTHENTIC, "Authentic" }, { BOOTSEQUENCE_FILESELECT, "File Select" }, { BOOTSEQUENCE_DEBUGWARPSCREEN, "Debug Warp Screen" }, { BOOTSEQUENCE_WARPPOINT, "Warp Point" }, }; const char* GetGameVersionString(uint32_t index) { uint32_t gameVersion = ResourceMgr_GetGameVersion(index); switch (gameVersion) { case OOT_NTSC_US_10: return "NTSC 1.0"; case OOT_NTSC_US_11: return "NTSC 1.1"; case OOT_NTSC_US_12: return "NTSC 1.2"; case OOT_NTSC_US_GC: return "NTSC-U GC"; case OOT_NTSC_JP_GC: return "NTSC-J GC"; case OOT_NTSC_JP_GC_CE: return "NTSC-J GC (Collector's Edition)"; case OOT_NTSC_US_MQ: return "NTSC-U MQ"; case OOT_NTSC_JP_MQ: return "NTSC-J MQ"; case OOT_PAL_10: return "PAL 1.0"; case OOT_PAL_11: return "PAL 1.1"; case OOT_PAL_GC: return "PAL GC"; case OOT_PAL_MQ: return "PAL MQ"; case OOT_PAL_GC_DBG1: case OOT_PAL_GC_DBG2: return "PAL GC-D"; case OOT_PAL_GC_MQ_DBG: return "PAL MQ-D"; case OOT_IQUE_CN: return "IQUE CN"; case OOT_IQUE_TW: return "IQUE TW"; default: return "UNKNOWN"; } } #include "message_data_static.h" extern "C" MessageTableEntry* sNesMessageEntryTablePtr; extern "C" MessageTableEntry* sGerMessageEntryTablePtr; extern "C" MessageTableEntry* sFraMessageEntryTablePtr; extern "C" MessageTableEntry* sJpnMessageEntryTablePtr; static const std::array messageTables = { &sNesMessageEntryTablePtr, &sGerMessageEntryTablePtr, &sFraMessageEntryTablePtr, &sJpnMessageEntryTablePtr }; void SohMenu::UpdateLanguageMap(std::map& languageMap) { for (int32_t i = LANGUAGE_ENG; i < LANGUAGE_MAX; i++) { if (*messageTables.at(i) != NULL) { if (!languageMap.contains(i)) { languageMap.insert(std::make_pair(i, languages.at(i))); } } else { languageMap.erase(i); } } } void SohMenu::AddMenuSettings() { // Add Settings Menu AddMenuEntry("Settings", CVAR_SETTING("Menu.SettingsSidebarSection")); AddSidebarEntry("Settings", "General", 2); WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; // General - Settings AddWidget(path, "Menu Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Menu.Theme")) .RaceDisable(false) .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) .RaceDisable(false) .Options(CheckboxOptions().Tooltip( "Allows controller navigation of the port 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, "Allow background inputs", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ALLOW_BACKGROUND_INPUTS) .RaceDisable(false) .Callback([](WidgetInfo& info) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, CVarGetInteger(CVAR_ALLOW_BACKGROUND_INPUTS, 1) ? "1" : "0"); }) .Options(CheckboxOptions() .Tooltip("Allows controller inputs to be picked up by the game even when the game window isn't " "the focused window.") .DefaultValue(1)); AddWidget(path, "Menu Background Opacity", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_SETTING("Menu.BackgroundOpacity")) .RaceDisable(false) .Options(FloatSliderOptions().DefaultValue(0.85f).IsPercentage().Tooltip( "Sets the opacity of the background of the port menu.")); AddWidget(path, "General Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("CursorVisibility")) .RaceDisable(false) .Callback([](WidgetInfo& info) { Ship::Context::GetRawInstance()->GetWindow()->SetForceCursorVisibility( CVarGetInteger(CVAR_SETTING("CursorVisibility"), 0)); }) .Options(CheckboxOptions().Tooltip("Makes the cursor always visible, even in full screen.")); #endif AddWidget(path, "Search In Sidebar", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("Menu.SidebarSearch")) .RaceDisable(false) .Callback([](WidgetInfo& info) { if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { mSohMenu->InsertSidebarSearch(); } else { mSohMenu->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(CVAR_SETTING("Menu.SearchAutofocus")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip( "Search input box gets autofocus when visible. Does not affect using other widgets.")); AddWidget(path, "Reset Button Combination:", WIDGET_CVAR_BTN_SELECTOR) .CVar("gSettings.ResetBtn") .Options(BtnSelectorOptions().DefaultValue(BTN_CUSTOM_MODIFIER2)); AddWidget(path, "Open App Files Folder", WIDGET_BUTTON) .RaceDisable(false) .Callback([](WidgetInfo& info) { std::string filesPath = Ship::Context::GetRawInstance()->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.")); AddWidget(path, "Boot", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Boot Sequence", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("BootSequence")) .RaceDisable(false) .Options(ComboboxOptions() .DefaultIndex(BOOTSEQUENCE_DEFAULT) .LabelPosition(LabelPositions::Far) .ComponentAlignment(ComponentAlignments::Right) .ComboMap(bootSequenceLabels) .Tooltip("Configure what happens when starting or resetting the game.\n\n" "Default: LUS logo -> N64 logo\n" "Authentic: N64 logo only\n" "File Select: Skip to file select menu\n" "Debug Warp Screen: Skip to the debug warp screen\n" "Warp Point: Skip to active warp point (if set), see Dev Tools -> General")); AddWidget(path, "Languages", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Translate Title Screen", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("TitleScreenTranslation")) .RaceDisable(false); AddWidget(path, "Language", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Languages")) .RaceDisable(false) .PreFunc([](WidgetInfo& info) { auto options = std::static_pointer_cast(info.options); SohMenu::UpdateLanguageMap(options->comboMap); }) .Options(ComboboxOptions() .LabelPosition(LabelPositions::Far) .ComponentAlignment(ComponentAlignments::Right) .ComboMap(languages) .DefaultIndex(LANGUAGE_ENG)); AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); #if defined(_WIN32) || defined(__APPLE__) || defined(ESPEAK) AddWidget(path, "Text to Speech", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yTTS")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Enables text to speech for in-game dialogue")); #endif AddWidget(path, "Disable Idle Camera Re-Centering", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yDisableIdleCam")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Disables the automatic re-centering of the camera when idle.")); AddWidget(path, "Disable Screen Flash for Finishing Blow", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yNoScreenFlashForFinishingBlow")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Disables the white screen flash on enemy kill.")); AddWidget(path, "Disable Jabu Wobble", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yNoJabuWobble")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Disable the geometry wobble and camera distortion inside Jabu.")); AddWidget(path, "Disable Heat Haze", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yNoHeatHaze")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Disable the heat haze distortion effect in Death Mountain / Fire Temple.")); AddWidget(path, "EXPERIMENTAL", WIDGET_SEPARATOR_TEXT).Options(TextOptions().Color(Colors::Orange)); AddWidget(path, "ImGui Menu Scaling", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("ImGuiScale")) .RaceDisable(false) .Options(ComboboxOptions() .ComboMap(imguiScaleOptions) .Tooltip("Changes the scaling of the ImGui menu elements.") .DefaultIndex(1) .ComponentAlignment(ComponentAlignments::Right) .LabelPosition(LabelPositions::Far)) .Callback([](WidgetInfo& info) { OTRGlobals::Instance->ScaleImGui(); }); // General - About path.column = SECTION_COLUMN_2; AddWidget(path, "About", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Ship Of Harkinian", WIDGET_TEXT); if (gGitCommitTag[0] != 0) { AddWidget(path, gBuildVersion, WIDGET_TEXT); } else { AddWidget(path, ("Branch: " + std::string(gGitBranch)), WIDGET_TEXT); AddWidget(path, ("Commit: " + std::string(gGitCommitHash)), WIDGET_TEXT); } for (uint32_t i = 0; i < ResourceMgr_GetNumGameVersions(); i++) { AddWidget(path, GetGameVersionString(i), WIDGET_TEXT); } // Audio Settings path.sidebarName = "Audio"; path.column = SECTION_COLUMN_1; AddSidebarEntry("Settings", "Audio", 3); AddWidget(path, "Master Volume: %d %%", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("Volume.Master")) .RaceDisable(false) .Options(IntSliderOptions().Min(0).Max(100).DefaultValue(40).ShowButtons(true).Format("")); AddWidget(path, "Main Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("Volume.MainMusic")) .RaceDisable(false) .Options(IntSliderOptions().Min(0).Max(100).DefaultValue(100).ShowButtons(true).Format("")) .Callback([](WidgetInfo& info) { Audio_SetGameVolume(SEQ_PLAYER_BGM_MAIN, ((float)CVarGetInteger(CVAR_SETTING("Volume.MainMusic"), 100) / 100.0f)); }); AddWidget(path, "Sub Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("Volume.SubMusic")) .RaceDisable(false) .Options(IntSliderOptions().Min(0).Max(100).DefaultValue(100).ShowButtons(true).Format("")) .Callback([](WidgetInfo& info) { Audio_SetGameVolume(SEQ_PLAYER_BGM_SUB, ((float)CVarGetInteger(CVAR_SETTING("Volume.SubMusic"), 100) / 100.0f)); }); AddWidget(path, "Fanfare Volume: %d %%", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("Volume.Fanfare")) .RaceDisable(false) .Options(IntSliderOptions().Min(0).Max(100).DefaultValue(100).ShowButtons(true).Format("")) .Callback([](WidgetInfo& info) { Audio_SetGameVolume(SEQ_PLAYER_FANFARE, ((float)CVarGetInteger(CVAR_SETTING("Volume.Fanfare"), 100) / 100.0f)); }); AddWidget(path, "Sound Effects Volume: %d %%", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("Volume.SFX")) .RaceDisable(false) .Options(IntSliderOptions().Min(0).Max(100).DefaultValue(100).ShowButtons(true).Format("")) .Callback([](WidgetInfo& info) { Audio_SetGameVolume(SEQ_PLAYER_SFX, ((float)CVarGetInteger(CVAR_SETTING("Volume.SFX"), 100) / 100.0f)); }); AddWidget(path, "Audio API (Needs reload)", WIDGET_AUDIO_BACKEND).RaceDisable(false); // Graphics Settings static int32_t maxFps = 360; const char* 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."; path.sidebarName = "Graphics"; AddSidebarEntry("Settings", "Graphics", 3); AddWidget(path, "Graphics Options", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Toggle Fullscreen", WIDGET_BUTTON) .RaceDisable(false) .Callback([](WidgetInfo& info) { Ship::Context::GetRawInstance()->GetWindow()->ToggleFullscreen(); }) .Options(ButtonOptions().Tooltip("Toggles Fullscreen On/Off.")); AddWidget(path, "Internal Resolution", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_INTERNAL_RESOLUTION) .RaceDisable(false) .Callback([](WidgetInfo& info) { Ship::Context::GetRawInstance()->GetWindow()->SetResolutionMultiplier( CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); }) .PreFunc([](WidgetInfo& info) { if (mSohMenu->disabledMap.at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active && mSohMenu->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 (mSohMenu->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() .Min(0.5f) .Max(2.0f)); #ifndef __WIIU__ AddWidget(path, "Anti-aliasing (MSAA)", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_MSAA_VALUE) .RaceDisable(false) .Callback([](WidgetInfo& info) { Ship::Context::GetRawInstance()->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 auto fps = CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); const char* fpsFormat = fps == 20 ? "Original (%d)" : "%d"; AddWidget(path, "Current FPS", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SETTING("InterpolationFPS")) .RaceDisable(false) .Callback([](WidgetInfo& info) { auto options = std::static_pointer_cast(info.options); int32_t defaultValue = options->defaultValue; if (CVarGetInteger(info.cVar, defaultValue) == defaultValue) { options->format = "Original (%d)"; } else { options->format = "%d"; } }) .PreFunc([](WidgetInfo& info) { if (mSohMenu->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).Format(fpsFormat)); AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("MatchRefreshRate")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Matches interpolation value to the refresh rate of your display.")); AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND).RaceDisable(false); AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_VSYNC_ENABLED) .RaceDisable(false) .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; }) .Options(CheckboxOptions() .Tooltip("Removes tearing, but clamps your max FPS to your display's refresh rate.") .DefaultValue(true)); AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SDL_WINDOWED_FULLSCREEN) .RaceDisable(false) .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->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) .RaceDisable(false) .PreFunc( [](WidgetInfo& info) { info.isHidden = mSohMenu->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.") .DefaultValue(true)); AddWidget(path, "Texture Filter (Needs reload)", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_TEXTURE_FILTER) .RaceDisable(false) .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap)); path.column = SECTION_COLUMN_2; AddWidget(path, "Advanced Graphics Options", WIDGET_SEPARATOR_TEXT); // Controls path.sidebarName = "Controls"; path.column = SECTION_COLUMN_1; AddSidebarEntry("Settings", "Controls", 2); AddWidget(path, "Clear Devices", WIDGET_BUTTON) .Callback([](WidgetInfo& info) { SohGui::mModalWindow->RegisterPopup( "Clear Config", "This will completely erase the controls config, including registered devices.\nContinue?", "Clear", "Cancel", []() { Ship::Context::GetRawInstance()->GetConsoleVariables()->ClearBlock(CVAR_PREFIX_SETTING ".Controllers"); uint8_t bits = 0; Ship::Context::GetRawInstance()->GetControlDeck()->Init(&bits); }, nullptr); }) .Options(ButtonOptions().Size(Sizes::Inline)); AddWidget(path, "Controller Bindings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("ControllerConfiguration")) .RaceDisable(false) .WindowName("Configure Controller") .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Bindings Window.")); // Input Viewer path.sidebarName = "Input Viewer"; AddSidebarEntry("Settings", path.sidebarName, 3); AddWidget(path, "Input Viewer", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Toggle Input Viewer", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("InputViewer")) .RaceDisable(false) .WindowName("Input Viewer") .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Toggles the Input Viewer.").EmbedWindow(false)); AddWidget(path, "Input Viewer Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Popout Input Viewer Settings", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("InputViewerSettings")) .RaceDisable(false) .WindowName("Input Viewer Settings") .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Input Viewer Settings Window.")); // Notifications path.sidebarName = "Notifications"; path.column = SECTION_COLUMN_1; AddSidebarEntry("Settings", path.sidebarName, 3); AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Notifications.Position")) .RaceDisable(false) .Options(ComboboxOptions() .Tooltip("Which corner of the screen notifications appear in.") .ComboMap(notificationPosition) .DefaultIndex(3)); AddWidget(path, "Duration (seconds):", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_SETTING("Notifications.Duration")) .RaceDisable(false) .Options(FloatSliderOptions() .Tooltip("How long notifications are displayed for.") .Format("%.1f") .Step(0.1f) .Min(3.0f) .Max(30.0f) .DefaultValue(10.0f)); AddWidget(path, "Background Opacity", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_SETTING("Notifications.BgOpacity")) .RaceDisable(false) .Options(FloatSliderOptions() .Tooltip("How opaque the background of notifications is.") .DefaultValue(0.5f) .IsPercentage()); AddWidget(path, "Size:", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_SETTING("Notifications.Size")) .RaceDisable(false) .Options(FloatSliderOptions() .Tooltip("How large notifications are.") .Format("%.1f") .Step(0.1f) .Min(1.0f) .Max(5.0f) .DefaultValue(1.8f)); AddWidget(path, "Test Notification", WIDGET_BUTTON) .RaceDisable(false) .Callback([](WidgetInfo& info) { Notification::Emit({ .itemIcon = "__OTR__textures/icon_item_24_static/gQuestIconGoldSkulltulaTex", .prefix = "This", .message = "is a", .suffix = "test.", }); }) .Options(ButtonOptions().Tooltip("Displays a test notification.")); AddWidget(path, "Mute Notification Sound", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("Notifications.Mute")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Prevent notifications from playing a sound.")); // Mod Menu path.sidebarName = "Mod Menu"; AddSidebarEntry("Settings", path.sidebarName, 1); AddWidget(path, "Popout Mod Menu Window", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("ModMenu")) .WindowName("Mod Menu") .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Mod Menu Window.")); } } // namespace SohGui