diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index f7bbbaa3b..4b892e6cd 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -200,6 +200,55 @@ void GuiMenu::openUIOptions() themeVariantsFunc(Settings::getInstance()->getString("ThemeSet"), Settings::getInstance()->getString("ThemeVariant")); + // Theme color schemes. + auto themeColorScheme = std::make_shared>( + getHelpStyle(), "THEME COLOR SCHEME", false); + s->addWithLabel("THEME COLOR SCHEME", themeColorScheme); + s->addSaveFunc([themeColorScheme, s] { + if (themeColorScheme->getSelected() != + Settings::getInstance()->getString("ThemeColorScheme")) { + Settings::getInstance()->setString("ThemeColorScheme", themeColorScheme->getSelected()); + s->setNeedsSaving(); + s->setNeedsReloading(); + s->setInvalidateCachedBackground(); + } + }); + + auto themeColorSchemesFunc = [=](const std::string& selectedTheme, + const std::string& selectedColorScheme) { + std::map::const_iterator + currentSet {themeSets.find(selectedTheme)}; + if (currentSet == themeSets.cend()) + return; + // We need to recreate the OptionListComponent entries. + themeColorScheme->clearEntries(); + if (currentSet->second.capabilities.colorSchemes.size() > 0) { + for (auto& colorScheme : currentSet->second.capabilities.colorSchemes) { + // If required, abbreviate the color scheme name so it doesn't overlap the + // setting name. + float maxNameLength {mSize.x * 0.52f}; + themeColorScheme->add(colorScheme.label, colorScheme.name, + colorScheme.name == selectedColorScheme, maxNameLength); + } + if (themeColorScheme->getSelectedObjects().size() == 0) + themeColorScheme->selectEntry(0); + } + else { + if (currentSet->second.capabilities.legacyTheme) + themeColorScheme->add("Legacy theme set", "none", true); + else + themeColorScheme->add("None defined", "none", true); + themeColorScheme->setEnabled(false); + themeColorScheme->setOpacity(DISABLED_OPACITY); + themeColorScheme->getParent() + ->getChild(themeColorScheme->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + } + }; + + themeColorSchemesFunc(Settings::getInstance()->getString("ThemeSet"), + Settings::getInstance()->getString("ThemeColorScheme")); + // Theme aspect ratios. auto themeAspectRatio = std::make_shared>( getHelpStyle(), "THEME ASPECT RATIO", false); @@ -667,6 +716,7 @@ void GuiMenu::openUIOptions() return; if (!firstRun) { themeVariantsFunc(themeName, themeVariant->getSelected()); + themeColorSchemesFunc(themeName, themeColorScheme->getSelected()); themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected()); } int selectableVariants {0}; @@ -688,7 +738,21 @@ void GuiMenu::openUIOptions() ->getChild(themeVariant->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } - + if (!selectedSet->second.capabilities.legacyTheme && + selectedSet->second.capabilities.colorSchemes.size() > 0) { + themeColorScheme->setEnabled(true); + themeColorScheme->setOpacity(1.0f); + themeColorScheme->getParent() + ->getChild(themeColorScheme->getChildIndex() - 1) + ->setOpacity(1.0f); + } + else { + themeColorScheme->setEnabled(false); + themeColorScheme->setOpacity(DISABLED_OPACITY); + themeColorScheme->getParent() + ->getChild(themeColorScheme->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + } if (!selectedSet->second.capabilities.legacyTheme && selectedSet->second.capabilities.aspectRatios.size() > 0) { themeAspectRatio->setEnabled(true); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 0cc30fc60..ab49cfb64 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -132,6 +132,7 @@ void Settings::setDefaults() // UI settings. mStringMap["ThemeSet"] = {"slate-DE", "slate-DE"}; mStringMap["ThemeVariant"] = {"", ""}; + mStringMap["ThemeColorScheme"] = {"", ""}; mStringMap["ThemeAspectRatio"] = {"", ""}; mStringMap["GamelistViewStyle"] = {"automatic", "automatic"}; mStringMap["TransitionStyle"] = {"slide", "slide"}; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 672bd6208..2e75c99b7 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -470,6 +470,18 @@ void ThemeData::loadFile(const std::map& sysDataMap, mVariants.emplace_back("all"); } + if (mCurrentThemeSet->second.capabilities.colorSchemes.size() > 0) { + for (auto& colorScheme : mCurrentThemeSet->second.capabilities.colorSchemes) + mColorSchemes.emplace_back(colorScheme.name); + + if (std::find(mColorSchemes.cbegin(), mColorSchemes.cend(), + Settings::getInstance()->getString("ThemeColorScheme")) != + mColorSchemes.cend()) + mSelectedColorScheme = Settings::getInstance()->getString("ThemeColorScheme"); + else + mSelectedColorScheme = mColorSchemes.front(); + } + if (mCurrentThemeSet->second.capabilities.aspectRatios.size() > 0) { if (std::find(mCurrentThemeSet->second.capabilities.aspectRatios.cbegin(), mCurrentThemeSet->second.capabilities.aspectRatios.cend(), @@ -490,6 +502,7 @@ void ThemeData::loadFile(const std::map& sysDataMap, if (!mLegacyTheme) { parseVariants(root); + parseColorSchemes(root); parseAspectRatios(root); } } @@ -609,7 +622,9 @@ void ThemeData::populateThemeSets() if (!capabilities.legacyTheme) { LOG(LogDebug) << "Theme set includes support for " << capabilities.variants.size() << " variant" - << (capabilities.variants.size() != 1 ? "s" : "") << " and " + << (capabilities.variants.size() != 1 ? "s" : "") << ", " + << capabilities.colorSchemes.size() << " color scheme" + << (capabilities.colorSchemes.size() != 1 ? "s" : "") << " and " << capabilities.aspectRatios.size() << " aspect ratio" << (capabilities.aspectRatios.size() != 1 ? "s" : ""); } @@ -727,10 +742,10 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& pugi::xml_document doc; #if defined(_WIN64) - pugi::xml_parse_result res = - doc.load_file(Utils::String::stringToWideString(capFile).c_str()); + pugi::xml_parse_result res { + doc.load_file(Utils::String::stringToWideString(capFile).c_str())}; #else - pugi::xml_parse_result res = doc.load_file(capFile.c_str()); + pugi::xml_parse_result res {doc.load_file(capFile.c_str())}; #endif if (res.status == pugi::status_no_document_element) { LOG(LogDebug) << "Found a capabilities.xml file with no configuration"; @@ -745,9 +760,9 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& return capabilities; } - for (pugi::xml_node aspectRatio = themeCapabilities.child("aspectRatio"); aspectRatio; + for (pugi::xml_node aspectRatio {themeCapabilities.child("aspectRatio")}; aspectRatio; aspectRatio = aspectRatio.next_sibling("aspectRatio")) { - std::string value = aspectRatio.text().get(); + std::string value {aspectRatio.text().get()}; if (std::find_if(sSupportedAspectRatios.cbegin(), sSupportedAspectRatios.cend(), [&value](const std::pair& entry) { return entry.first == value; @@ -768,7 +783,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& } } - for (pugi::xml_node variant = themeCapabilities.child("variant"); variant; + for (pugi::xml_node variant {themeCapabilities.child("variant")}; variant; variant = variant.next_sibling("variant")) { ThemeVariant readVariant; std::string name {variant.attribute("name").as_string()}; @@ -873,6 +888,55 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& capabilities.variants.emplace_back(readVariant); } } + + for (pugi::xml_node colorScheme {themeCapabilities.child("colorScheme")}; colorScheme; + colorScheme = colorScheme.next_sibling("colorScheme")) { + ThemeColorScheme readColorScheme; + std::string name {colorScheme.attribute("name").as_string()}; + if (name.empty()) { + LOG(LogWarning) + << "Found tag without name attribute, ignoring entry in \"" + << capFile << "\""; + } + else { + readColorScheme.name = name; + } + + pugi::xml_node labelTag {colorScheme.child("label")}; + if (labelTag == nullptr) { + LOG(LogDebug) << "No colorScheme