diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index 286a5aa7..e9051e21 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -23,6 +23,11 @@ std::shared_ptr mA void SetupGuiElements() { auto gui = LUS::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("gOpenMenuBar", CVarGetInteger("gOpenMenuBar", 0)); gui->SetMenuBar(mGameMenuBar); mStatsWindow = gui->GetGuiWindow("Stats"); @@ -73,8 +78,8 @@ static const char* filters[3] = { }; void DrawSettingsMenu(){ - if(ImGui::BeginMenu("Settings")){ - if (ImGui::BeginMenu("Audio")) { + if(UIWidgets::BeginMenu("Settings")){ + if (UIWidgets::BeginMenu("Audio")) { UIWidgets::PaddedEnhancementSliderFloat("Master Volume: %d %%", "##Master_Vol", "gGameMasterVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true); if (UIWidgets::PaddedEnhancementSliderFloat("Main Music Volume: %d %%", "##Main_Music_Vol", "gMainMusicVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true)) { audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f)); @@ -116,25 +121,19 @@ void DrawSettingsMenu(){ UIWidgets::Spacer(0); - if (ImGui::BeginMenu("Controller")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2 (12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (GameUI::mInputEditorWindow) { - if (ImGui::Button("Controller Mapping", ImVec2 (-1.0f, 0.0f))) { - GameUI::mInputEditorWindow->ToggleVisibility(); - } - } - UIWidgets::PaddedSeparator(); - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); + if (UIWidgets::BeginMenu("Controller")) { + UIWidgets::WindowButton("Controller Mapping", "gInputEditorWindow", GameUI::mInputEditorWindow); + + UIWidgets::Spacer(0); + #ifndef __SWITCH__ - UIWidgets::EnhancementCheckbox("Menubar Controller Navigation", "gControlNav"); - UIWidgets::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"); + 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::PaddedEnhancementCheckbox("Show Inputs", "gInputEnabled", true, false); - UIWidgets::Tooltip("Shows currently pressed inputs on the bottom right of the screen"); + UIWidgets::CVarCheckbox("Show Inputs", "gInputEnabled", { + .tooltip = "Shows currently pressed inputs on the bottom right of the screen" + }); UIWidgets::PaddedEnhancementSliderFloat("Input Scale: %.1f", "##Input", "gInputScale", 1.0f, 3.0f, "", 1.0f, false, true, true, false); UIWidgets::Tooltip("Sets the on screen size of the displayed inputs from the Show Inputs setting"); @@ -144,31 +143,22 @@ void DrawSettingsMenu(){ ImGui::EndMenu(); } - UIWidgets::Spacer(0); - ImGui::SetCursorPosY(0.0f); - if (ImGui::BeginMenu("Graphics")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - UIWidgets::Spacer(0); - if (GameUI::mAdvancedResolutionSettingsWindow) { - if (ImGui::Button(GameUI::GetWindowButtonText("Advanced Resolution", CVarGetInteger("gAdvancedResolutionEditorEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - GameUI::mAdvancedResolutionSettingsWindow->ToggleVisibility(); - } - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); + if (UIWidgets::BeginMenu("Graphics")) { + UIWidgets::WindowButton("Resolution Editor", "gAdvancedResolutionEditorEnabled", GameUI::mAdvancedResolutionSettingsWindow); - UIWidgets::Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective form of anti-aliasing"); - LUS::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1)); + UIWidgets::Spacer(0); + + // Previously was running every frame, and nothing was setting it? Maybe a bad copy/paste? + // LUS::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__ - UIWidgets::PaddedEnhancementSliderInt("MSAA: %d", "##IMSAA", "gMSAAValue", 1, 8, "", 1, true, true, false); - UIWidgets::Tooltip("Activates multi-sample anti-aliasing when above 1x up to 8x for 8 samples for every pixel"); - LUS::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1)); + 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" + })) { + LUS::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1)); + } #endif - UIWidgets::Tooltip("Matches interpolation value to the current game's window refresh rate"); { // FPS Slider const int minFps = 30; @@ -361,8 +351,8 @@ void DrawMenuBarIcon() { ImVec2 iconSize = ImVec2(16.0f * 2, 16.0f * 2); float posScale = 2.0f; #else - ImVec2 iconSize = ImVec2(16.0f, 16.0f); - float posScale = 1.0f; + ImVec2 iconSize = ImVec2(20.0f, 20.0f); + float posScale = 1.5f; #endif ImGui::SetCursorPos(ImVec2(5, 2.5f) * posScale); ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Game_Icon"), iconSize); @@ -372,8 +362,8 @@ void DrawMenuBarIcon() { } void DrawGameMenu() { - if (ImGui::BeginMenu("Ghostship")) { - if (ImGui::MenuItem("Reset", + if (UIWidgets::BeginMenu("Ghostship")) { + if (UIWidgets::MenuItem("Reset", #ifdef __APPLE__ "Command-R" #else @@ -384,11 +374,11 @@ void DrawGameMenu() { } #if !defined(__SWITCH__) && !defined(__WIIU__) - if (ImGui::MenuItem("Toggle Fullscreen", "F9")) { + if (UIWidgets::MenuItem("Toggle Fullscreen", "F9")) { LUS::Context::GetInstance()->GetWindow()->ToggleFullscreen(); } - if (ImGui::MenuItem("Quit")) { + if (UIWidgets::MenuItem("Quit")) { LUS::Context::GetInstance()->GetWindow()->Close(); } #endif @@ -397,17 +387,21 @@ void DrawGameMenu() { } void DrawEnhancementsMenu() { - if (ImGui::BeginMenu("Enhancements")) { + if (UIWidgets::BeginMenu("Enhancements")) { - if (ImGui::BeginMenu("Gameplay")) { - UIWidgets::PaddedEnhancementCheckbox("No Level of Detail (LOD)", "gDisableLOD", true, false); - UIWidgets::Tooltip("Disable Level of Detail (LOD) to avoid models using lower poly versions at a distance"); - UIWidgets::PaddedEnhancementCheckbox("Select any star from menu", "gSelectAllStars", true, false); - UIWidgets::Tooltip("Let's you select any star from the menu regardless of the courses completion status."); - UIWidgets::PaddedEnhancementCheckbox("Collecting Stars Will Not Exit Level", "gStarNoExit", true, false); - UIWidgets::Tooltip("Stars act like the 100 coin star and will not take you out of the level"); - UIWidgets::PaddedEnhancementCheckbox("Avoid playing peach cutscene", "gDisablePeachCutscene", true, false); - UIWidgets::Tooltip("Avoid playing the peach cutscene when starting a new game"); + if (UIWidgets::BeginMenu("Gameplay")) { + 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("Select any star from menu", "gSelectAllStars", { + .tooltip = "Let's you select any star from the menu regardless of the courses completion status." + }); + UIWidgets::CVarCheckbox("Collecting Stars Will Not Exit Level", "gStarNoExit", { + .tooltip = "Stars act like the 100 coin star and will not take you out of the level" + }); + UIWidgets::CVarCheckbox("Avoid playing peach cutscene", "gDisablePeachCutscene", { + .tooltip = "Avoid playing the peach cutscene when starting a new game" + }); ImGui::EndMenu(); } @@ -416,47 +410,28 @@ void DrawEnhancementsMenu() { } void DrawCheatsMenu() { - if (ImGui::BeginMenu("Cheats")) { - UIWidgets::PaddedEnhancementCheckbox("Infinite Health", "gInfiniteHealth", true, false); - UIWidgets::PaddedEnhancementCheckbox("Infinite Lives", "gInfiniteLives", true, false); - + if (UIWidgets::BeginMenu("Cheats")) { + UIWidgets::CVarCheckbox("Infinite Health", "gInfiniteHealth"); + UIWidgets::CVarCheckbox("Infinite Lives", "gInfiniteLives"); ImGui::EndMenu(); } } void DrawDebugMenu() { - if (ImGui::BeginMenu("Developer")) { + if (UIWidgets::BeginMenu("Developer")) { + UIWidgets::CVarCheckbox("Debug mode", "gEnableDebugMode", { + .tooltip = "Various debug features, including a level selector from the main menu" + }); - UIWidgets::PaddedEnhancementCheckbox("Enable level selector", "gEnableDebugMode", true, false); - UIWidgets::Tooltip("Enable the level selector in the main menu"); UIWidgets::Spacer(0); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (GameUI::mStatsWindow) { - if (ImGui::Button( - GameUI::GetWindowButtonText("Stats", CVarGetInteger("gStatsEnabled", 0)).c_str(), - ImVec2(-1.0f, 0.0f))) { - GameUI::mStatsWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Shows the stats window, with your FPS and frametimes, and the OS " - "you're playing on"); - } - if (GameUI::mConsoleWindow) { - if (ImGui::Button( - GameUI::GetWindowButtonText("Console", CVarGetInteger("gConsoleEnabled", 0)) - .c_str(), - ImVec2(-1.0f, 0.0f))) { - GameUI::mConsoleWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Enables the console window, allowing you to input commands, type " - "help for some examples"); - } - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); + 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(); } @@ -466,10 +441,6 @@ void GameMenuBar::DrawElement() { if(ImGui::BeginMenuBar()){ DrawMenuBarIcon(); - static ImVec2 sWindowPadding(8.0f, 8.0f); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, sWindowPadding); - DrawGameMenu(); ImGui::SetCursorPosY(0.0f); @@ -488,7 +459,6 @@ void GameMenuBar::DrawElement() { DrawDebugMenu(); - ImGui::PopStyleVar(1); ImGui::EndMenuBar(); } } \ No newline at end of file diff --git a/src/port/ui/UIWidgets.cpp b/src/port/ui/UIWidgets.cpp index a5a2f934..a4ad5416 100644 --- a/src/port/ui/UIWidgets.cpp +++ b/src/port/ui/UIWidgets.cpp @@ -596,4 +596,353 @@ namespace UIWidgets { } ImGui::PopID(); } + + // V2 + + void PushStyleMenu(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, UIWidgets::Colors::DarkGray); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::Colors::DarkGray); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f); + } + + void PopStyleMenu() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); + } + + bool BeginMenu(const char* label, const ImVec4& color) { + bool dirty = false; + PushStyleMenu(color); + if (ImGui::BeginMenu(label)) { + dirty = true; + } + PopStyleMenu(); + return dirty; + } + + void PushStyleMenuItem(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f)); + } + + void PopStyleMenuItem() { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); + } + + bool MenuItem(const char* label, const char* shortcut, const ImVec4& color) { + bool dirty = false; + PushStyleMenuItem(color); + if (ImGui::MenuItem(label, shortcut)) { + dirty = true; + } + PopStyleMenuItem(); + return dirty; + } + + void PushStyleButton(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); + } + + void PopStyleButton() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(4); + } + + bool Button(const char* label, const ButtonOptions& options) { + ImGui::BeginDisabled(options.disabled); + PushStyleButton(options.color); + bool dirty = ImGui::Button(label, options.size); + PopStyleButton(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + return dirty; + } + + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const ButtonOptions& options) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + std::string buttonText = label; + bool dirty = false; + if (CVarGetInteger(cvarName, 0)) { + buttonText = ICON_FA_WINDOW_CLOSE " " + buttonText; + } else { + buttonText = ICON_FA_EXTERNAL_LINK_SQUARE " " + buttonText; + } + if (Button(buttonText.c_str(), options)) { + windowPtr->ToggleVisibility(); + dirty = true; + } + ImGui::PopStyleVar(); + return dirty; + } + + void PushStyleCheckbox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); + } + + void PopStyleCheckbox() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(5); + } + + bool Checkbox(const char* label, bool* value, const CheckboxOptions& options) { + ImGui::PushID(label); + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + const char* invisibleLabel = ("##" + std::string(label)).c_str(); + ImGui::BeginDisabled(options.disabled); + PushStyleCheckbox(options.color); + if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x); + } else if (options.labelPosition == LabelPosition::Above) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + } + } + dirty = ImGui::Checkbox(invisibleLabel, value); + if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(); + ImGui::SetCursorPosX(startX); + ImGui::Text(label); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + } + } + PopStyleCheckbox(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) { + bool dirty = false; + bool value = (bool)CVarGetInteger(cvarName, options.defaultValue); + if (Checkbox(label, &value, options)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } + + void PushStyleCombobox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + } + + void PopStyleCombobox() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(9); + } + + bool Combobox(const char* label, uint8_t* value, std::span comboArray, const ComboboxOptions& options) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + const char* invisibleLabel = ("##" + std::string(label)).c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboArray[*value], options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (uint8_t i = 0; i < comboArray.size(); i++) { + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], i == *value)) { + *value = i; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text(label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCombobox(const char* label, const char* cvarName, std::span comboArray, const ComboboxOptions& options) { + bool dirty = false; + uint8_t value = (uint8_t)CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboArray, options)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } + + void PushStyleSlider(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(1.0, 1.0, 1.0, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(1.0, 1.0, 1.0, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + } + + void PopStyleSlider() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(6); + } + + bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue, const SliderOptions& options){ + bool dirty = false; + const char* invisibleLabel = ("##" + std::string(label)).c_str(); + int32_t value = CVarGetInteger(cvarName, defaultValue); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, value); + } + } 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, value); + } + } + if (options.showButtons) { + if (Button("-", { .size = Sizes::Inline, .color = options.color }) && value > min) { + value--; + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize("+").x + 20.0f + 3.0f)); + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, &value, &min, &max, options.format, options.flags)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", { .size = Sizes::Inline, .color = options.color }) && value < max) { + value++; + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } } diff --git a/src/port/ui/UIWidgets.h b/src/port/ui/UIWidgets.h index bdaf63b4..58b8f2dd 100644 --- a/src/port/ui/UIWidgets.h +++ b/src/port/ui/UIWidgets.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace UIWidgets { @@ -87,4 +88,105 @@ namespace UIWidgets { 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 Sizes { + const ImVec2 Inline = ImVec2(0.0f, 0.0f); + const ImVec2 Fill = ImVec2(-1.0f, 0.0f); + } + + enum LabelPosition { + Near, + Far, + Above, + None, + Within, + }; + + enum ComponentAlignment { + Left, + Right, + }; + + void PushStyleMenu(const ImVec4& color = Colors::Indigo); + void PopStyleMenu(); + bool BeginMenu(const char* label, const ImVec4& color = Colors::Indigo); + + void PushStyleMenuItem(const ImVec4& color = Colors::Indigo); + void PopStyleMenuItem(); + bool MenuItem(const char* label, const char* shortcut = NULL, const ImVec4& color = Colors::Indigo); + + struct ButtonOptions { + const ImVec4 color = Colors::Gray; + const ImVec2 size = Sizes::Fill; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + }; + + void PushStyleButton(const ImVec4& color = Colors::Gray); + void PopStyleButton(); + bool Button(const char* label, const ButtonOptions& options = {}); + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const ButtonOptions& options = {}); + + struct CheckboxOptions { + const ImVec4 color = Colors::Indigo; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + bool defaultValue = false; // Only applicable to CVarCheckbox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Near; + }; + + void PushStyleCheckbox(const ImVec4& color = Colors::Indigo); + void PopStyleCheckbox(); + bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {}); + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {}); + + struct ComboboxOptions { + const ImVec4 color = Colors::Indigo; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + uint32_t defaultIndex = 0; // Only applicable to CVarCombobox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + ImGuiComboFlags flags = 0; + }; + + void PushStyleCombobox(const ImVec4& color = Colors::Indigo); + void PopStyleCombobox(); + bool Combobox(const char* label, std::span comboArray, const ComboboxOptions& options = {}); + bool CVarCombobox(const char* label, const char* cvarName, std::span comboArray, const ComboboxOptions& options = {}); + + struct SliderOptions { + const ImVec4 color = Colors::Green; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + bool showButtons = true; + ImGuiSliderFlags flags = 0; + const char* format = "%d"; + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + }; + + void PushStyleSlider(const ImVec4& color = Colors::Indigo); + void PopStyleSlider(); + bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue, const SliderOptions& options = {}); + bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue, const SliderOptions& options = {}); } \ No newline at end of file