diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 233b37125..2b01e356e 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -239,7 +239,7 @@ SystemData::SystemData(const std::string& name, mPlaceholder = new FileData(PLACEHOLDER, "", getSystemEnvData(), this); setIsGameSystemStatus(); - loadTheme(); + loadTheme(ThemeTriggers::TriggerType::NONE); } SystemData::~SystemData() @@ -1304,7 +1304,7 @@ std::pair SystemData::getDisplayedGameCount() const return mRootFolder->getGameCount(); } -void SystemData::loadTheme() +void SystemData::loadTheme(ThemeTriggers::TriggerType trigger) { mTheme = std::make_shared(); @@ -1354,7 +1354,7 @@ void SystemData::loadTheme() sysData.insert(std::pair("system.theme.collections", "\b")); } - mTheme->loadFile(sysData, path, isCustomCollection()); + mTheme->loadFile(sysData, path, trigger, isCustomCollection()); } catch (ThemeException& e) { LOG(LogError) << e.what() << " (system \"" << mName << "\", theme \"" << mThemeFolder diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index 53573308b..8edb6d786 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -13,6 +13,7 @@ #define ES_APP_SYSTEM_DATA_H #include "PlatformId.h" +#include "ThemeData.h" #include #include @@ -134,8 +135,8 @@ public: void sortSystem(bool reloadGamelist = true, bool jumpToFirstRow = false); - // Load or re-load theme. - void loadTheme(); + // Load or reload theme. + void loadTheme(ThemeTriggers::TriggerType trigger); FileFilterIndex* getIndex() { return mFilterIndex; } void onMetaDataSavePoint(); diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 5a82f428e..dace0cc9e 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -739,22 +739,22 @@ std::shared_ptr ViewController::getGamelistView(SystemData* system // If there's no entry, then create it and return it. std::shared_ptr view; - bool themeHasVideoView {system->getTheme()->hasView("video")}; - - // Decide which view style to use. - GamelistViewStyle selectedViewStyle {AUTOMATIC}; - - std::string viewPreference {Settings::getInstance()->getString("GamelistViewStyle")}; - if (viewPreference == "basic") - selectedViewStyle = BASIC; - else if (viewPreference == "detailed") - selectedViewStyle = DETAILED; - else if (viewPreference == "video") - selectedViewStyle = VIDEO; - if (system->getTheme()->isLegacyTheme()) { + const bool themeHasVideoView {system->getTheme()->hasView("video")}; + + // Decide which view style to use. + GamelistViewStyle selectedViewStyle {AUTOMATIC}; + + const std::string& viewPreference {Settings::getInstance()->getString("GamelistViewStyle")}; + if (viewPreference == "basic") + selectedViewStyle = BASIC; + else if (viewPreference == "detailed") + selectedViewStyle = DETAILED; + else if (viewPreference == "video") + selectedViewStyle = VIDEO; + if (selectedViewStyle == AUTOMATIC) { - std::vector files { + const std::vector files { system->getRootFolder()->getFilesRecursive(GAME | FOLDER)}; for (auto it = files.cbegin(); it != files.cend(); ++it) { @@ -768,29 +768,125 @@ std::shared_ptr ViewController::getGamelistView(SystemData* system } } } + // Create the view. + switch (selectedViewStyle) { + case VIDEO: { + mState.viewstyle = VIDEO; + break; + } + case DETAILED: { + mState.viewstyle = DETAILED; + break; + } + case BASIC: { + } + default: { + if (!system->isGroupedCustomCollection()) + mState.viewstyle = BASIC; + break; + } + } } + else { + // Variant triggers. + const auto overrides = system->getTheme()->getCurrentThemeSetSelectedVariantOverrides(); - // Create the view. - switch (selectedViewStyle) { - case VIDEO: { - mState.viewstyle = VIDEO; - break; - } - case DETAILED: { - mState.viewstyle = DETAILED; - break; - } - case BASIC: { - } - default: { - if (!system->isGroupedCustomCollection()) - mState.viewstyle = BASIC; - break; + if (!overrides.empty()) { + ThemeTriggers::TriggerType noVideosTriggerType {ThemeTriggers::TriggerType::NONE}; + ThemeTriggers::TriggerType noMediaTriggerType {ThemeTriggers::TriggerType::NONE}; + + const std::vector files { + system->getRootFolder()->getFilesRecursive(GAME | FOLDER)}; + + if (overrides.find(ThemeTriggers::TriggerType::NO_VIDEOS) != overrides.end()) { + noVideosTriggerType = ThemeTriggers::TriggerType::NO_VIDEOS; + + for (auto it = files.cbegin(); it != files.cend(); ++it) { + if (!(*it)->getVideoPath().empty()) { + noVideosTriggerType = ThemeTriggers::TriggerType::NONE; + break; + } + } + } + + if (overrides.find(ThemeTriggers::TriggerType::NO_MEDIA) != overrides.end()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NO_MEDIA; + + for (auto& imageType : overrides.at(ThemeTriggers::TriggerType::NO_MEDIA).second) { + for (auto it = files.cbegin(); it != files.cend(); ++it) { + if (imageType == "miximage") { + if (!(*it)->getMiximagePath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "marquee") { + if (!(*it)->getMarqueePath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "screenshot") { + if (!(*it)->getScreenshotPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "titlescreen") { + if (!(*it)->getTitleScreenPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "cover") { + if (!(*it)->getCoverPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "backcover") { + if (!(*it)->getBackCoverPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "3dbox") { + if (!(*it)->get3DBoxPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "physicalmedia") { + if (!(*it)->getPhysicalMediaPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "fanart") { + if (!(*it)->getFanArtPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + else if (imageType == "video") { + if (!(*it)->getVideoPath().empty()) { + noMediaTriggerType = ThemeTriggers::TriggerType::NONE; + goto BREAK; + } + } + } + } + } + BREAK: + // noMedia takes precedence over the noVideos trigger. + if (noMediaTriggerType == ThemeTriggers::TriggerType::NO_MEDIA) + system->loadTheme(noMediaTriggerType); + else + system->loadTheme(noVideosTriggerType); } } view = std::shared_ptr(new GamelistView(system->getRootFolder())); - view->setTheme(system->getTheme()); std::vector& sysVec = SystemData::sSystemVector; @@ -970,7 +1066,7 @@ void ViewController::preload() // Load navigation sounds, either from the theme if it supports it, or otherwise from // the bundled fallback sound files. - bool themeSoundSupport = false; + bool themeSoundSupport {false}; for (SystemData* system : SystemData::sSystemVector) { if (system->getTheme()->hasView("all")) { NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get()); @@ -1000,7 +1096,7 @@ void ViewController::reloadGamelistView(GamelistView* view, bool reloadTheme) mCurrentView = nullptr; if (reloadTheme) - system->loadTheme(); + system->loadTheme(ThemeTriggers::TriggerType::NONE); system->getIndex()->setKidModeFilters(); std::shared_ptr newView {getGamelistView(system)}; @@ -1047,15 +1143,18 @@ void ViewController::reloadAll() // Load themes, create GamelistViews and reset filters. for (auto it = cursorMap.cbegin(); it != cursorMap.cend(); ++it) { - it->first->loadTheme(); + it->first->loadTheme(ThemeTriggers::TriggerType::NONE); it->first->getIndex()->resetFilters(); - getGamelistView(it->first)->setCursor(it->second); } // Rebuild SystemListView. mSystemListView.reset(); getSystemListView(); + // Restore cursor positions for all systems. + for (auto it = cursorMap.cbegin(); it != cursorMap.cend(); ++it) + getGamelistView(it->first)->setCursor(it->second); + // Update mCurrentView since the pointers changed. if (mState.viewing == GAMELIST) { mCurrentView = getGamelistView(mState.getSystem()); @@ -1073,7 +1172,7 @@ void ViewController::reloadAll() // Load navigation sounds, either from the theme if it supports it, or otherwise from // the bundled fallback sound files. NavigationSounds::getInstance().deinit(); - bool themeSoundSupport = false; + bool themeSoundSupport {false}; for (SystemData* system : SystemData::sSystemVector) { if (system->getTheme()->hasView("all")) { NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get()); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index cbb9dd02d..cbc940904 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -28,6 +28,18 @@ std::vector ThemeData::sSupportedViews { {"system"}, {"gamelist"}}; +std::vector ThemeData::sSupportedMediaTypes { + {"miximage"}, + {"marquee"}, + {"screenshot"}, + {"titlescreen"}, + {"cover"}, + {"backcover"}, + {"3dbox"}, + {"physicalmedia"}, + {"fanart"}, + {"video"}}; + std::vector ThemeData::sLegacySupportedViews { {"all"}, {"system"}, @@ -497,9 +509,11 @@ ThemeData::ThemeData() void ThemeData::loadFile(const std::map& sysDataMap, const std::string& path, + const ThemeTriggers::TriggerType trigger, const bool customCollection) { mCustomCollection = customCollection; + mOverrideVariant = ""; mPaths.push_back(path); @@ -565,6 +579,12 @@ void ThemeData::loadFile(const std::map& sysDataMap, mSelectedVariant = mVariants.front(); // Special shortcut variant to apply configuration to all defined variants. mVariants.emplace_back("all"); + + if (trigger != ThemeTriggers::TriggerType::NONE) { + auto overrides = getCurrentThemeSetSelectedVariantOverrides(); + if (overrides.find(trigger) != overrides.end()) + mOverrideVariant = overrides.at(trigger).first; + } } if (mCurrentThemeSet->second.capabilities.colorSchemes.size() > 0) { @@ -809,6 +829,21 @@ const std::string ThemeData::getAspectRatioLabel(const std::string& aspectRatio) return "invalid ratio"; } +const std::map>> +ThemeData::getCurrentThemeSetSelectedVariantOverrides() +{ + const auto variantIter = std::find_if( + mCurrentThemeSet->second.capabilities.variants.cbegin(), + mCurrentThemeSet->second.capabilities.variants.cend(), + [this](ThemeVariant currVariant) { return currVariant.name == mSelectedVariant; }); + + if (variantIter != mCurrentThemeSet->second.capabilities.variants.cend() && + !(*variantIter).overrides.empty()) + return (*variantIter).overrides; + else + return ThemeVariant().overrides; +} + unsigned int ThemeData::getHexColor(const std::string& str) { ThemeException error; @@ -816,20 +851,20 @@ unsigned int ThemeData::getHexColor(const std::string& str) if (str == "") throw error << "Empty color property"; - size_t len {str.size()}; - if (len != 6 && len != 8) + const size_t length {str.size()}; + if (length != 6 && length != 8) throw error << "Invalid color property \"" << str << "\" (must be 6 or 8 characters in length)"; - unsigned int val; + unsigned int value; std::stringstream ss; ss << str; - ss >> std::hex >> val; + ss >> std::hex >> value; - if (len == 6) - val = (val << 8) | 0xFF; + if (length == 6) + value = (value << 8) | 0xFF; - return val; + return value; } std::string ThemeData::resolvePlaceholders(const std::string& in) @@ -854,18 +889,19 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& { ThemeCapability capabilities; std::vector aspectRatiosTemp; + bool hasTriggers {false}; - std::string capFile {path + "/capabilities.xml"}; + const std::string capFile {path + "/capabilities.xml"}; if (Utils::FileSystem::isRegularFile(capFile) || Utils::FileSystem::isSymlink(capFile)) { capabilities.legacyTheme = false; pugi::xml_document doc; #if defined(_WIN64) - pugi::xml_parse_result res { + const 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())}; + const 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"; @@ -874,7 +910,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& LOG(LogError) << "Couldn't parse capabilities.xml: " << res.description(); return capabilities; } - pugi::xml_node themeCapabilities {doc.child("themeCapabilities")}; + const pugi::xml_node& themeCapabilities {doc.child("themeCapabilities")}; if (!themeCapabilities) { LOG(LogError) << "Missing tag in capabilities.xml"; return capabilities; @@ -882,7 +918,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& for (pugi::xml_node aspectRatio {themeCapabilities.child("aspectRatio")}; aspectRatio; aspectRatio = aspectRatio.next_sibling("aspectRatio")) { - std::string value {aspectRatio.text().get()}; + const std::string& value {aspectRatio.text().get()}; if (std::find_if(sSupportedAspectRatios.cbegin(), sSupportedAspectRatios.cend(), [&value](const std::pair& entry) { return entry.first == value; @@ -906,7 +942,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& for (pugi::xml_node variant {themeCapabilities.child("variant")}; variant; variant = variant.next_sibling("variant")) { ThemeVariant readVariant; - std::string name {variant.attribute("name").as_string()}; + const std::string& name {variant.attribute("name").as_string()}; if (name.empty()) { LOG(LogWarning) << "Found tag without name attribute, ignoring entry in \"" << capFile @@ -921,7 +957,7 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& readVariant.name = name; } - pugi::xml_node labelTag {variant.child("label")}; + const pugi::xml_node& labelTag {variant.child("label")}; if (labelTag == nullptr) { LOG(LogDebug) << "No variant