diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index b69d4c40d..f13a37354 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -670,9 +670,8 @@ void GamelistView::updateView(const CursorState& state) } } else { - for (auto& image : mImageComponents) { + for (auto& image : mImageComponents) setGameImage(file, image.get()); - } for (auto& video : mVideoComponents) { setGameImage(file, video.get()); @@ -1018,74 +1017,79 @@ void GamelistView::setGameImage(FileData* file, GuiComponent* comp) path = file->getImagePath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "miximage") { path = file->getMiximagePath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "marquee") { path = file->getMarqueePath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "screenshot") { path = file->getScreenshotPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "titlescreen") { path = file->getTitleScreenPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "cover") { path = file->getCoverPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "backcover") { path = file->getBackCoverPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "3dbox") { path = file->get3DBoxPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "physicalmedia") { path = file->getPhysicalMediaPath(); if (path != "") { comp->setImage(path); - break; + return; } } else if (imageType == "fanart") { path = file->getFanArtPath(); if (path != "") { comp->setImage(path); - break; + return; } } } // This is needed so the default image is set if no game media was found. - if (path == "" && (comp->getThemeImageTypes().size() > 0 || comp->getDefaultImage() != "")) + if (path == "" && (comp->getThemeImageTypes().size() > 0 || comp->getDefaultImage() != "")) { comp->setImage(""); + return; + } + + // Sets per-game overrides of static images using the game file basename. + comp->setGameOverrideImage(Utils::FileSystem::getStem(file->getPath()), file->getSystemName()); } diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index e95987d02..215bc2155 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -279,6 +279,7 @@ public: const std::string& getThemeGameSelector() const { return mThemeGameSelector; } const unsigned int getThemeGameSelectorEntry() const { return mThemeGameSelectorEntry; } virtual const std::string getDefaultImage() const { return ""; } + virtual void setGameOverrideImage(const std::string& basename, const std::string& system) {} const float getThemeOpacity() const { return mThemeOpacity; } virtual std::shared_ptr getFont() const { return nullptr; } diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 03b5c3d1c..44434034d 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -299,6 +299,7 @@ std::map> {"flipHorizontal", BOOLEAN}, {"flipVertical", BOOLEAN}, {"path", PATH}, + {"gameOverridePath", PATH}, {"default", PATH}, {"imageType", STRING}, {"metadataElement", BOOLEAN}, diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 2e2c2554d..8ada78604 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -128,6 +128,25 @@ void ImageComponent::setImage(const std::shared_ptr& texture, b resize(); } +void ImageComponent::setGameOverrideImage(const std::string& basename, const std::string& system) +{ + if (mGameOverridePath == "") + return; + + if (!Utils::FileSystem::exists(mGameOverridePath + system)) + return; + + const std::string imageFilePath {mGameOverridePath + system + "/" + basename}; + for (auto& extension : sSupportedOverrideExtensions) { + if (Utils::FileSystem::exists(imageFilePath + extension)) { + setImage(imageFilePath + extension); + return; + } + } + + setImage(mGameOverrideOriginalPath); +} + void ImageComponent::setResize(const float width, const float height) { mTargetSize = glm::vec2 {width, height}; @@ -494,7 +513,45 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, } } - if (elem->has("default")) + if (properties && elem->has("imageType")) { + std::string imageTypes {elem->get("imageType")}; + for (auto& character : imageTypes) { + if (std::isspace(character)) + character = ','; + } + imageTypes = Utils::String::replace(imageTypes, ",,", ","); + mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ","); + + if (mThemeImageTypes.empty()) { + LOG(LogError) << "ImageComponent: Invalid theme configuration, property \"imageType\" " + "for element \"" + << element.substr(6) << "\" contains no values"; + } + + for (std::string& type : mThemeImageTypes) { + if (std::find(sSupportedImageTypes.cbegin(), sSupportedImageTypes.cend(), type) == + sSupportedImageTypes.cend()) { + LOG(LogError) + << "ImageComponent: Invalid theme configuration, property \"imageType\" " + "for element \"" + << element.substr(6) << "\" defined as \"" << type << "\""; + mThemeImageTypes.clear(); + break; + } + } + + std::vector sortedTypes {mThemeImageTypes}; + std::stable_sort(sortedTypes.begin(), sortedTypes.end()); + + if (std::adjacent_find(sortedTypes.begin(), sortedTypes.end()) != sortedTypes.end()) { + LOG(LogError) << "ImageComponent: Invalid theme configuration, property \"imageType\" " + "for element \"" + << element.substr(6) << "\" contains duplicate values"; + mThemeImageTypes.clear(); + } + } + + if (!mThemeImageTypes.empty() && elem->has("default")) setDefaultImage(elem->get("default")); bool tile {elem->has("tile") && elem->get("tile")}; @@ -567,42 +624,20 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, if (tile && updateAlignment) updateVertices(); - if (properties && elem->has("imageType")) { - std::string imageTypes {elem->get("imageType")}; - for (auto& character : imageTypes) { - if (std::isspace(character)) - character = ','; - } - imageTypes = Utils::String::replace(imageTypes, ",,", ","); - mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ","); + // Per-game overrides of static images using the game file's basename. It's by design not + // possible to override scraped media. + if (mThemeImageTypes.empty() && elem->has("gameOverridePath")) { + mGameOverridePath = elem->get("gameOverridePath"); +#if defined(_WIN64) + mBasenamePath = Utils::String::replace(mGameOverridePath, "\\", "/"); +#endif + if (mGameOverridePath.back() != '/') + mGameOverridePath.push_back('/'); - if (mThemeImageTypes.empty()) { - LOG(LogError) << "ImageComponent: Invalid theme configuration, property \"imageType\" " - "for element \"" - << element.substr(6) << "\" contains no values"; - } - - for (std::string& type : mThemeImageTypes) { - if (std::find(sSupportedImageTypes.cbegin(), sSupportedImageTypes.cend(), type) == - sSupportedImageTypes.cend()) { - LOG(LogError) - << "ImageComponent: Invalid theme configuration, property \"imageType\" " - "for element \"" - << element.substr(6) << "\" defined as \"" << type << "\""; - mThemeImageTypes.clear(); - break; - } - } - - std::vector sortedTypes {mThemeImageTypes}; - std::stable_sort(sortedTypes.begin(), sortedTypes.end()); - - if (std::adjacent_find(sortedTypes.begin(), sortedTypes.end()) != sortedTypes.end()) { - LOG(LogError) << "ImageComponent: Invalid theme configuration, property \"imageType\" " - "for element \"" - << element.substr(6) << "\" contains duplicate values"; - mThemeImageTypes.clear(); - } + if (elem->has("path")) + mGameOverrideOriginalPath = elem->get("path"); + else + mGameOverrideOriginalPath = ""; } if (elem->has("metadataElement") && elem->get("metadataElement")) diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index d14f22eb4..152058af9 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -30,6 +30,9 @@ public: // Use an already existing texture. void setImage(const std::shared_ptr& texture, bool resizeTexture = true); + // Sets per-game overrides of static images using the game file basename. + void setGameOverrideImage(const std::string& basename, const std::string& system) override; + void setDynamic(bool state) { mDynamic = state; } void onSizeChanged() override { updateVertices(); } @@ -143,7 +146,11 @@ private: bool mColorGradientHorizontal; std::string mDefaultPath; + std::string mGameOverridePath; + std::string mGameOverrideOriginalPath; + static inline std::vector sSupportedOverrideExtensions {".jpg", ".png", ".gif", + ".svg"}; static inline std::vector sSupportedImageTypes { "image", "miximage", "marquee", "screenshot", "titlescreen", "cover", "backcover", "3dbox", "physicalmedia", "fanart"};