diff --git a/src/Feature.h b/src/Feature.h index 5abbdad0d1..fda2f5cb51 100644 --- a/src/Feature.h +++ b/src/Feature.h @@ -44,6 +44,12 @@ struct Feature */ virtual bool IsCore() const { return false; } + /** + * Get the category for UI grouping (e.g., "Terrain", "Lighting", "Characters", etc.) + * Core features will be distributed to their respective categories + */ + virtual std::string_view GetCategory() const { return "Other"; } + /** * Whether the feature will show up in the GUI menu */ diff --git a/src/Features/CloudShadows.h b/src/Features/CloudShadows.h index e090d18f29..742f0f4459 100644 --- a/src/Features/CloudShadows.h +++ b/src/Features/CloudShadows.h @@ -23,6 +23,7 @@ struct CloudShadows : Feature virtual inline std::string GetName() override { return "Cloud Shadows"; } virtual inline std::string GetShortName() override { return "CloudShadows"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } + virtual std::string_view GetCategory() const override { return "Sky"; } virtual inline std::string_view GetShaderDefineName() override { return "CLOUD_SHADOWS"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/DynamicCubemaps.h b/src/Features/DynamicCubemaps.h index 2f40f453b3..fc9d300481 100644 --- a/src/Features/DynamicCubemaps.h +++ b/src/Features/DynamicCubemaps.h @@ -102,6 +102,7 @@ struct DynamicCubemaps : Feature virtual inline std::string GetName() override { return "Dynamic Cubemaps"; } virtual inline std::string GetShortName() override { return "DynamicCubemaps"; } virtual inline std::string_view GetShaderDefineName() override { return "DYNAMIC_CUBEMAPS"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/ExtendedMaterials.h b/src/Features/ExtendedMaterials.h index 92cb49d3c2..92ea44d77d 100644 --- a/src/Features/ExtendedMaterials.h +++ b/src/Features/ExtendedMaterials.h @@ -11,6 +11,7 @@ struct ExtendedMaterials : Feature virtual inline std::string GetName() override { return "Extended Materials"; } virtual inline std::string GetShortName() override { return "ExtendedMaterials"; } virtual inline std::string_view GetShaderDefineName() override { return "EXTENDED_MATERIALS"; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/GrassCollision.h b/src/Features/GrassCollision.h index 4d21052cc7..1d6f41d3d8 100644 --- a/src/Features/GrassCollision.h +++ b/src/Features/GrassCollision.h @@ -16,6 +16,7 @@ struct GrassCollision : Feature virtual inline std::string GetShortName() override { return "GrassCollision"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "GRASS_COLLISION"; } + virtual std::string_view GetCategory() const override { return "Grass"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/GrassLighting.h b/src/Features/GrassLighting.h index 37d9bdf5f4..3db8aeb9db 100644 --- a/src/Features/GrassLighting.h +++ b/src/Features/GrassLighting.h @@ -11,12 +11,12 @@ struct GrassLighting : Feature static GrassLighting singleton; return &singleton; } - virtual inline std::string GetName() override { return "Grass Lighting"; } virtual inline std::string GetShortName() override { return "GrassLighting"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "GRASS_LIGHTING"; } virtual bool HasShaderDefine(RE::BSShader::Type shaderType) override { return shaderType == RE::BSShader::Type::Grass; }; + virtual std::string_view GetCategory() const override { return "Grass"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/HairSpecular.h b/src/Features/HairSpecular.h index 9add34385b..66791c2741 100644 --- a/src/Features/HairSpecular.h +++ b/src/Features/HairSpecular.h @@ -15,6 +15,7 @@ struct HairSpecular : Feature virtual inline std::string GetName() override { return "Hair Specular"; } virtual inline std::string GetShortName() override { return "HairSpecular"; } virtual inline std::string_view GetShaderDefineName() override { return "CS_HAIR"; } + virtual std::string_view GetCategory() const override { return "Characters"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/IBL.h b/src/Features/IBL.h index 2be55290f9..88e5709a3d 100644 --- a/src/Features/IBL.h +++ b/src/Features/IBL.h @@ -14,6 +14,7 @@ struct IBL : Feature virtual inline std::string GetName() override { return "Image Based Lighting"; } virtual inline std::string GetShortName() override { return "ImageBasedLighting"; } virtual inline std::string_view GetShaderDefineName() override { return "IBL"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/InteriorSunShadows.h b/src/Features/InteriorSunShadows.h index b300a80a2e..35694ca24d 100644 --- a/src/Features/InteriorSunShadows.h +++ b/src/Features/InteriorSunShadows.h @@ -11,6 +11,7 @@ struct InteriorSunShadows : Feature virtual inline std::string GetName() override { return "Interior Sun Shadows"; } virtual inline std::string GetShortName() override { return "InteriorSunShadows"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/InverseSquareLighting.h b/src/Features/InverseSquareLighting.h index 27cad6014b..6ff712c388 100644 --- a/src/Features/InverseSquareLighting.h +++ b/src/Features/InverseSquareLighting.h @@ -16,6 +16,8 @@ struct InverseSquareLighting : Feature virtual inline std::string_view GetShaderDefineName() override { return "ISL"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } + virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/LODBlending.h b/src/Features/LODBlending.h index 02954ca965..a213f25dc2 100644 --- a/src/Features/LODBlending.h +++ b/src/Features/LODBlending.h @@ -11,6 +11,7 @@ struct LODBlending : Feature virtual inline std::string GetName() override { return "LOD Blending"; } virtual inline std::string GetShortName() override { return "LODBlending"; } virtual inline std::string_view GetShaderDefineName() override { return "LOD_BLENDING"; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/LightLimitFix.h b/src/Features/LightLimitFix.h index 77bb08a3db..5f4a979162 100644 --- a/src/Features/LightLimitFix.h +++ b/src/Features/LightLimitFix.h @@ -18,6 +18,7 @@ struct LightLimitFix : Feature virtual inline std::string GetShortName() override { return "LightLimitFix"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "LIGHT_LIMIT_FIX"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/ScreenSpaceGI.h b/src/Features/ScreenSpaceGI.h index 0daed38e5e..b3931a6865 100644 --- a/src/Features/ScreenSpaceGI.h +++ b/src/Features/ScreenSpaceGI.h @@ -18,6 +18,7 @@ struct ScreenSpaceGI : Feature virtual inline std::string GetShortName() override { return "ScreenSpaceGI"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "SSGI"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual inline bool HasShaderDefine(RE::BSShader::Type t) override { return t == RE::BSShader::Type::Lighting || diff --git a/src/Features/ScreenSpaceShadows.h b/src/Features/ScreenSpaceShadows.h index 80c6559f01..d5db9e530f 100644 --- a/src/Features/ScreenSpaceShadows.h +++ b/src/Features/ScreenSpaceShadows.h @@ -16,6 +16,7 @@ struct ScreenSpaceShadows : Feature virtual inline std::string GetShortName() override { return "ScreenSpaceShadows"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "SCREEN_SPACE_SHADOWS"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/SkySync.h b/src/Features/SkySync.h index 4605184fd9..3da3cd9884 100644 --- a/src/Features/SkySync.h +++ b/src/Features/SkySync.h @@ -11,6 +11,7 @@ struct SkySync : Feature virtual inline std::string GetName() override { return "Sky Sync"; } virtual inline std::string GetShortName() override { return "SkySync"; } + virtual std::string_view GetCategory() const override { return "Sky"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/Skylighting.h b/src/Features/Skylighting.h index cf17cdc62e..083cf9d75a 100644 --- a/src/Features/Skylighting.h +++ b/src/Features/Skylighting.h @@ -18,6 +18,7 @@ struct Skylighting : Feature virtual inline std::string GetShortName() override { return "Skylighting"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "SKYLIGHTING"; } + virtual std::string_view GetCategory() const override { return "Sky"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/SubsurfaceScattering.h b/src/Features/SubsurfaceScattering.h index ebf4c4308f..55650d3cca 100644 --- a/src/Features/SubsurfaceScattering.h +++ b/src/Features/SubsurfaceScattering.h @@ -66,6 +66,7 @@ struct SubsurfaceScattering : Feature virtual inline std::string GetShortName() override { return "SubsurfaceScattering"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "SSS"; } + virtual std::string_view GetCategory() const override { return "Characters"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/TerrainBlending.h b/src/Features/TerrainBlending.h index 43d5f70910..66086f3b00 100644 --- a/src/Features/TerrainBlending.h +++ b/src/Features/TerrainBlending.h @@ -12,6 +12,7 @@ struct TerrainBlending : Feature virtual inline std::string GetName() override { return "Terrain Blending"; } virtual inline std::string GetShortName() override { return "TerrainBlending"; } virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_BLENDING"; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/TerrainHelper.h b/src/Features/TerrainHelper.h index fb18f16c49..4ec2e5d41c 100644 --- a/src/Features/TerrainHelper.h +++ b/src/Features/TerrainHelper.h @@ -15,6 +15,7 @@ struct TerrainHelper : Feature virtual inline std::string GetName() override { return "Terrain Helper"; } virtual inline std::string GetShortName() override { return "TerrainHelper"; } virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_HELPER"; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/TerrainShadows.h b/src/Features/TerrainShadows.h index cd1e359305..04c7d0a7ff 100644 --- a/src/Features/TerrainShadows.h +++ b/src/Features/TerrainShadows.h @@ -18,6 +18,7 @@ struct TerrainShadows : public Feature virtual inline std::string GetShortName() override { return "TerrainShadows"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_SHADOWS"; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { return { diff --git a/src/Features/TerrainVariation.h b/src/Features/TerrainVariation.h index 9ef1339b17..51b61eb41f 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -11,7 +11,6 @@ struct TerrainVariation : Feature static TerrainVariation singleton; return &singleton; } - virtual inline std::string GetName() override { return "Terrain Variation"; } virtual inline std::string GetShortName() override { return "TerrainVariation"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } @@ -19,6 +18,7 @@ struct TerrainVariation : Feature virtual inline bool HasShaderDefine(RE::BSShader::Type shaderType) override { return shaderType == RE::BSShader::Type::Lighting; } virtual bool IsCore() const override { return false; }; virtual bool SupportsVR() override { return true; } + virtual std::string_view GetCategory() const override { return "Landscape & Textures"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/VolumetricLighting.h b/src/Features/VolumetricLighting.h index 3f51845897..166e27466c 100644 --- a/src/Features/VolumetricLighting.h +++ b/src/Features/VolumetricLighting.h @@ -32,6 +32,7 @@ struct VolumetricLighting : Feature virtual inline std::string GetName() override { return "Volumetric Lighting"; } virtual inline std::string GetShortName() override { return "VolumetricLighting"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/WaterEffects.h b/src/Features/WaterEffects.h index 1909e93276..d31aed576a 100644 --- a/src/Features/WaterEffects.h +++ b/src/Features/WaterEffects.h @@ -15,11 +15,11 @@ struct WaterEffects : Feature } winrt::com_ptr causticsView; - virtual inline std::string GetName() override { return "Water Effects"; } virtual inline std::string GetShortName() override { return "WaterEffects"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "WATER_EFFECTS"; } + virtual std::string_view GetCategory() const override { return "Water"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Features/WetnessEffects.h b/src/Features/WetnessEffects.h index e64ffd4290..63c738ce53 100644 --- a/src/Features/WetnessEffects.h +++ b/src/Features/WetnessEffects.h @@ -16,6 +16,7 @@ struct WetnessEffects : Feature virtual inline std::string GetShortName() override { return "WetnessEffects"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "WETNESS_EFFECTS"; } + virtual std::string_view GetCategory() const override { return "Water"; } virtual std::pair> GetFeatureSummary() override { diff --git a/src/Menu.cpp b/src/Menu.cpp index a49df20a73..effb8415f6 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -21,6 +21,7 @@ #include "Util.h" #include "Features/LightLimitFix/ParticleLights.h" +#include "Utils/UI.h" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Menu::ThemeSettings::PaletteColors, @@ -31,9 +32,20 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Menu::ThemeSettings::StatusPaletteColors, Disable, - RestartNeeded, Error, - CurrentHotkey) + Warning, + RestartNeeded, + CurrentHotkey, + SuccessColor, + InfoColor) + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + Menu::ThemeSettings::FeatureHeadingColors, + LineColorDefault, + LineColorHovered, + TextColorDefault, + TextColorHovered, + TextColorWhite) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Menu::Settings::PerfOverlaySettings, @@ -98,6 +110,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( GlobalScale, UseSimplePalette, Palette, + StatusPalette, + FeatureHeading, Style, FullPalette) @@ -350,15 +364,21 @@ void Menu::DrawSettings() ImGui::TableSetupColumn("##ListOfMenus", 0, 2); ImGui::TableSetupColumn("##MenuConfig", 0, 8); - static size_t selectedMenu = 0; - - // some type erasure bs for virtual-free polymorphism + static size_t selectedMenu = 0; // some type erasure bs for virtual-free polymorphism struct BuiltInMenu { std::string name; std::function func; }; - using MenuFuncInfo = std::variant; + struct CategoryHeader + { + std::string name; + }; + using MenuFuncInfo = std::variant; + + // Static storage for category expansion states + static std::map categoryExpansionStates; + struct ListMenuVisitor { size_t listId; @@ -382,7 +402,24 @@ void Menu::DrawSettings() } void operator()(const std::string& label) { - ImGui::SeparatorText(label.c_str()); + // Style "Unloaded Features" to match category headers + if (label == "Unloaded Features") { + Util::DrawSectionHeader(label.c_str(), true); + } else { + // Use default separator text for other labels + ImGui::SeparatorText(label.c_str()); + } + } + void operator()(const CategoryHeader& header) + { + // Get expansion state from static map + bool isExpanded = categoryExpansionStates[header.name]; + + // Draw category header with custom styling using util:UI function + Util::DrawCategoryHeader(header.name.c_str(), isExpanded); + + // Update expansion state + categoryExpansionStates[header.name] = isExpanded; } void operator()(Feature* feat) { @@ -465,6 +502,11 @@ void Menu::DrawSettings() // std::unreachable() from c++23 // you are not supposed to have selected a label! } + void operator()(const CategoryHeader&) + { + // Category headers are not selectable in the right panel + ImGui::TextDisabled("Please select a feature from the left."); + } void operator()(Feature* feat) { const auto featureName = feat->GetShortName(); @@ -561,21 +603,63 @@ void Menu::DrawSettings() BuiltInMenu{ "General", [&]() { DrawGeneralSettings(); } }, BuiltInMenu{ "Advanced", [&]() { DrawAdvancedSettings(); } }, BuiltInMenu{ "Display", [&]() { DrawDisplaySettings(); } } - }; + }; // NOTE: The menu list is rebuilt every frame, so category expansion states + // persist correctly. This is acceptable since the list is small and built + // infrequently, but could be optimized if performance becomes an issue. + + // Group features by category + std::map> categorizedFeatures; + for (Feature* feat : sortedFeatureList) { + if (feat->IsInMenu() && feat->loaded) { + std::string category(feat->GetCategory()); + categorizedFeatures[category].push_back(feat); + } + } + + // Sort features within each category + for (auto& [category, features] : categorizedFeatures) { + std::ranges::sort(features, [](Feature* a, Feature* b) { + return a->GetName() < b->GetName(); + }); + } + + // Define category order + std::vector categoryOrder = { "Characters", "Grass", "Lighting", "Sky", "Landscape & Textures", "Water", "Other" }; + // Add categorized features to menu with collapsible headers + for (const std::string& category : categoryOrder) { + if (categorizedFeatures.find(category) != categorizedFeatures.end() && !categorizedFeatures[category].empty()) { + // Initialize expansion state if not exists + if (categoryExpansionStates.find(category) == categoryExpansionStates.end()) { + categoryExpansionStates[category] = true; // Default to expanded + } + + // Add category header + menuList.push_back(CategoryHeader{ category }); + + // Add features only if category is expanded + if (categoryExpansionStates[category]) { + std::ranges::copy(categorizedFeatures[category], std::back_inserter(menuList)); + } + } + } - menuList.push_back("Core Features"s); - std::ranges::copy( - sortedFeatureList | std::ranges::views::filter([](Feature* feat) { - return feat->IsCore() && feat->IsInMenu() && feat->loaded; - }), - std::back_inserter(menuList)); + // Add any categories not in the predefined order + for (const auto& [category, features] : categorizedFeatures) { + if (std::find(categoryOrder.begin(), categoryOrder.end(), category) == categoryOrder.end() && !features.empty()) { + // Initialize expansion state if not exists + if (categoryExpansionStates.find(category) == categoryExpansionStates.end()) { + categoryExpansionStates[category] = true; // Default to expanded + } - menuList.push_back("Features"s); - std::ranges::copy( - sortedFeatureList | std::ranges::views::filter([](Feature* feat) { - return !feat->IsCore() && feat->IsInMenu() && feat->loaded; - }), - std::back_inserter(menuList)); + // Add category header + menuList.push_back(CategoryHeader{ category }); + + // Add features only if category is expanded + if (categoryExpansionStates[category]) { + std::ranges::copy(features, std::back_inserter(menuList)); + } + } + } auto unloadedFeatures = sortedFeatureList | std::ranges::views::filter([](Feature* feat) { return !feat->loaded && feat->IsInMenu(); @@ -799,6 +883,16 @@ void Menu::DrawGeneralSettings() ImGui::ColorEdit4("Warning Text", (float*)&themeSettings.StatusPalette.Warning); ImGui::ColorEdit4("Restart Needed Text", (float*)&themeSettings.StatusPalette.RestartNeeded); ImGui::ColorEdit4("Current Hotkey Text", (float*)&themeSettings.StatusPalette.CurrentHotkey); + ImGui::ColorEdit4("Success Text", (float*)&themeSettings.StatusPalette.SuccessColor); + ImGui::ColorEdit4("Info Text", (float*)&themeSettings.StatusPalette.InfoColor); + + ImGui::SeparatorText("Feature Headings"); + + ImGui::ColorEdit4("Line Color Default", (float*)&themeSettings.FeatureHeading.LineColorDefault); + ImGui::ColorEdit4("Line Color Hovered", (float*)&themeSettings.FeatureHeading.LineColorHovered); + ImGui::ColorEdit4("Text Color Default", (float*)&themeSettings.FeatureHeading.TextColorDefault); + ImGui::ColorEdit4("Text Color Hovered", (float*)&themeSettings.FeatureHeading.TextColorHovered); + ImGui::ColorEdit4("Text Color White", (float*)&themeSettings.FeatureHeading.TextColorWhite); ImGui::SeparatorText("Palette"); diff --git a/src/Menu.h b/src/Menu.h index 7767547fe2..41c330ed34 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -65,7 +65,17 @@ class Menu ImVec4 Warning{ 1.0f, 0.6f, 0.2f, 1.0f }; ImVec4 RestartNeeded{ 0.5f, 1.f, 0.5f, 1.f }; ImVec4 CurrentHotkey{ 1.f, 1.f, 0.f, 1.f }; + ImVec4 SuccessColor{ 0.0f, 1.0f, 0.0f, 1.0f }; + ImVec4 InfoColor{ 0.0f, 0.5f, 1.0f, 1.0f }; } StatusPalette; + struct FeatureHeadingColors + { + ImU32 LineColorDefault{ IM_COL32(120, 120, 120, 255) }; + ImU32 LineColorHovered{ IM_COL32(100, 100, 100, 255) }; + ImU32 TextColorDefault{ IM_COL32(180, 180, 180, 255) }; + ImU32 TextColorHovered{ IM_COL32(140, 140, 140, 255) }; + ImU32 TextColorWhite{ IM_COL32(255, 255, 255, 255) }; + } FeatureHeading; ImGuiStyle Style = []() { ImGuiStyle style = {}; diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 19811fc732..eb57aec6c6 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -1,4 +1,5 @@ #include "UI.h" +#include "Menu.h" namespace Util { @@ -98,4 +99,95 @@ namespace Util { return m_shouldDraw; } + + bool DrawCategoryHeader(const char* categoryName, bool& isExpanded) + { + // Draw category header with custom styling + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float availableWidth = ImGui::GetContentRegionAvail().x; + ImVec2 textSize = ImGui::CalcTextSize(categoryName); + + // Calculate line positions + float lineY = pos.y + textSize.y * 0.5f; + float lineLength = (availableWidth - textSize.x - 20.0f) * 0.5f; // 20px for padding + + // Create selectable area for the entire header + ImGui::PushID(categoryName); + bool hovered = false; + bool clicked = false; + + // Invisible button for hover detection and clicking + ImGui::SetCursorScreenPos(pos); + if (ImGui::InvisibleButton("##CategoryHeader", ImVec2(availableWidth, textSize.y + 4.0f))) { + clicked = true; + } + hovered = ImGui::IsItemHovered(); + + // Draw the lines and text using Menu theme colors + auto& theme = Menu::GetSingleton()->GetTheme().FeatureHeading; + ImU32 lineColor = hovered ? theme.LineColorHovered : theme.LineColorDefault; + ImU32 textColor = hovered ? theme.TextColorHovered : theme.TextColorDefault; + + // Left line + if (lineLength > 0) { + drawList->AddLine(ImVec2(pos.x, lineY), ImVec2(pos.x + lineLength, lineY), lineColor, 1.0f); + } + + // Right line + float rightLineStart = pos.x + lineLength + 10.0f + textSize.x + 10.0f; + if (rightLineStart < pos.x + availableWidth) { + drawList->AddLine(ImVec2(rightLineStart, lineY), ImVec2(pos.x + availableWidth, lineY), lineColor, 1.0f); + } + + // Center text + ImVec2 textPos = ImVec2(pos.x + lineLength + 10.0f, pos.y + 2.0f); + drawList->AddText(textPos, textColor, categoryName); + + // Handle click to toggle expansion + if (clicked) { + isExpanded = !isExpanded; + } + + ImGui::PopID(); + + // Move cursor to next line + ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + textSize.y + 8.0f)); + return clicked; + } + + void DrawSectionHeader(const char* sectionName, bool useWhiteText) + { + // Draw custom styled header similar to CategoryHeader but non-collapsible + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float availableWidth = ImGui::GetContentRegionAvail().x; + ImVec2 textSize = ImGui::CalcTextSize(sectionName); + + // Calculate line positions + float lineY = pos.y + textSize.y * 0.5f; + float lineLength = (availableWidth - textSize.x - 20.0f) * 0.5f; // 20px for padding + // Use Menu theme colors for consistent styling + auto& theme = Menu::GetSingleton()->GetTheme().FeatureHeading; + ImU32 lineColor = theme.LineColorDefault; + ImU32 textColor = useWhiteText ? theme.TextColorWhite : theme.TextColorDefault; + + // Left line + if (lineLength > 0) { + drawList->AddLine(ImVec2(pos.x, lineY), ImVec2(pos.x + lineLength, lineY), lineColor, 1.0f); + } + + // Right line + float rightLineStart = pos.x + lineLength + 10.0f + textSize.x + 10.0f; + if (rightLineStart < pos.x + availableWidth) { + drawList->AddLine(ImVec2(rightLineStart, lineY), ImVec2(pos.x + availableWidth, lineY), lineColor, 1.0f); + } + + // Center text + ImVec2 textPos = ImVec2(pos.x + lineLength + 10.0f, pos.y + 2.0f); + drawList->AddText(textPos, textColor, sectionName); + + // Move cursor to next line + ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + textSize.y + 8.0f)); + } } // namespace Util diff --git a/src/Utils/UI.h b/src/Utils/UI.h index 4dcd5d4264..bb74ecd8f8 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace Util { @@ -108,6 +110,21 @@ namespace Util bool PercentageSlider(const char* label, float* data, float lb = 0.f, float ub = 100.f, const char* format = "%.1f %%"); ImVec2 GetNativeViewportSizeScaled(float scale); + /** + * Draws a custom styled collapsible category header with lines extending from both sides + * @param categoryName The name of the category to display + * @param isExpanded Reference to the expansion state + * @return true if the expansion state was toggled + */ + bool DrawCategoryHeader(const char* categoryName, bool& isExpanded); + + /** + * Draws a custom styled section header (non-collapsible) with lines extending from both sides + * @param sectionName The name of the section to display + * @param useWhiteText Whether to use white text (for differentiation) + */ + void DrawSectionHeader(const char* sectionName, bool useWhiteText = false); + class PerformanceOverlay { public: