// SPDX-License-Identifier: MIT // // ES-DE Frontend // GamelistView.cpp // // Main gamelist logic. // #include "views/GamelistView.h" #include "CollectionSystemsManager.h" #include "UIModeController.h" #include "animations/LambdaAnimation.h" #include "utils/LocalizationUtil.h" #define FADE_IN_START_OPACITY 0.5f #define FADE_IN_TIME 325 GamelistView::GamelistView(FileData* root) : GamelistBase {root} , mRenderer {Renderer::getInstance()} , mStaticVideoAudio {false} { } GamelistView::~GamelistView() { // Remove theme extras. for (auto extra : mThemeExtras) { removeChild(extra); delete extra; } mThemeExtras.clear(); } const std::pair GamelistView::getDescriptionSystemNameSuffix() const { bool suffix {false}; LetterCase letterCase {LetterCase::UPPERCASE}; for (auto& text : mContainerTextComponents) { if (text->getThemeMetadata() == "description" && text->getSystemNameSuffix()) { suffix = true; letterCase = text->getLetterCaseSystemNameSuffix(); break; } } return std::make_pair(suffix, letterCase); } void GamelistView::onFileChanged(FileData* file, bool reloadGamelist) { if (reloadGamelist) { // Might switch to a detailed view. ViewController::getInstance()->reloadGamelistView(this); return; } // We could be tricky here to be efficient; // but this shouldn't happen very often so we'll just always repopulate. FileData* cursor {getCursor()}; if (!cursor->isPlaceHolder()) { populateList(cursor->getParent()->getChildrenListToDisplay(), cursor->getParent()); // Needed to avoid some minor transition animation glitches. if (mGrid != nullptr) mGrid->setSuppressTransitions(true); setCursor(cursor); if (mGrid != nullptr) mGrid->setSuppressTransitions(false); } else { populateList(mRoot->getChildrenListToDisplay(), mRoot); setCursor(cursor); } onDemandTextureLoad(); } void GamelistView::onShow() { for (auto& animation : mLottieAnimComponents) animation->resetComponent(); for (auto& animation : mGIFAnimComponents) animation->resetComponent(); for (auto& video : mStaticVideoComponents) video->stopVideoPlayer(); mLastUpdated = nullptr; GuiComponent::onShow(); updateView(CursorState::CURSOR_STOPPED); mPrimary->finishAnimation(0); mPrimary->onShowPrimary(); } void GamelistView::onHide() { for (auto& video : mVideoComponents) video->stopVideoPlayer(false); for (auto& video : mStaticVideoComponents) video->stopVideoPlayer(false); } void GamelistView::onTransition() { for (auto& animation : mLottieAnimComponents) animation->setPauseAnimation(true); for (auto& animation : mGIFAnimComponents) animation->setPauseAnimation(true); mWindow->renderListScrollOverlay(0.0f, ""); } void GamelistView::onThemeChanged(const std::shared_ptr& theme) { auto themes = ThemeData::getThemes(); std::map::const_iterator selectedTheme {themes.find(Settings::getInstance()->getString("Theme"))}; assert(selectedTheme != themes.cend()); mStaticVideoAudio = false; const bool isStartupSystem {Settings::getInstance()->getString("StartupSystem") == mRoot->getSystem()->getName()}; using namespace ThemeFlags; if (mTheme->hasView("gamelist")) { for (auto& element : mTheme->getViewElements("gamelist").elements) { if (element.second.type == "carousel" || element.second.type == "grid" || element.second.type == "textlist") { if (element.second.type == "carousel" && (mGrid != nullptr || mTextList != nullptr)) { LOG(LogWarning) << "SystemView::populate(): Multiple primary components " << "defined, skipping carousel configuration entry"; continue; } if (element.second.type == "grid" && (mCarousel != nullptr || mTextList != nullptr)) { LOG(LogWarning) << "SystemView::populate(): Multiple primary components " << "defined, skipping grid configuration entry"; continue; } if (element.second.type == "textlist" && (mCarousel != nullptr || mGrid != nullptr)) { LOG(LogWarning) << "SystemView::populate(): Multiple primary components " << "defined, skipping textlist configuration entry"; continue; } } if (element.second.type == "textlist") { if (mTextList == nullptr) { mTextList = std::make_unique>(); mPrimary = mTextList.get(); } mPrimary->setCursorChangedCallback( [&](const CursorState& state) { updateView(state); }); mPrimary->setDefaultZIndex(50.0f); mPrimary->setZIndex(50.0f); mPrimary->applyTheme(theme, "gamelist", element.first, ALL); addChild(mPrimary); } if (element.second.type == "carousel") { if (mCarousel == nullptr) { mCarousel = std::make_unique>(); if (element.second.has("defaultImage")) mCarousel->setDefaultImage(element.second.get("defaultImage")); if (element.second.has("defaultFolderImage")) mCarousel->setDefaultFolderImage( element.second.get("defaultFolderImage")); mPrimary = mCarousel.get(); } mPrimary->setCursorChangedCallback( [&](const CursorState& state) { updateView(state); }); mPrimary->setDefaultZIndex(50.0f); mPrimary->applyTheme(theme, "gamelist", element.first, ALL); addChild(mPrimary); } if (element.second.type == "grid") { if (mGrid == nullptr) { mGrid = std::make_unique>(); if (element.second.has("defaultImage")) mGrid->setDefaultImage(element.second.get("defaultImage")); if (element.second.has("defaultFolderImage")) mGrid->setDefaultFolderImage( element.second.get("defaultFolderImage")); mPrimary = mGrid.get(); } mPrimary->setCursorChangedCallback( [&](const CursorState& state) { updateView(state); }); mPrimary->setDefaultZIndex(50.0f); mPrimary->applyTheme(theme, "gamelist", element.first, ALL); addChild(mPrimary); } if (element.second.type == "image" && !(element.second.has("visible") && !element.second.get("visible"))) { // If this is the startup system, then forceload the images to avoid texture pop-in. if (isStartupSystem) mImageComponents.push_back(std::make_unique(true)); else mImageComponents.push_back(std::make_unique()); mImageComponents.back()->setDefaultZIndex(30.0f); mImageComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mImageComponents.back()->getThemeImageTypes().size() != 0) mImageComponents.back()->setScrollHide(true); else if (mImageComponents.back()->getMetadataElement()) mImageComponents.back()->setScrollHide(true); addChild(mImageComponents.back().get()); } else if (element.second.type == "video" && !(element.second.has("visible") && !element.second.get("visible"))) { if (element.second.has("path")) { mStaticVideoComponents.push_back(std::make_unique()); mStaticVideoComponents.back()->setDefaultZIndex(30.0f); mStaticVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mStaticVideoComponents.back()->getMetadataElement()) mStaticVideoComponents.back()->setScrollHide(true); mStaticVideoComponents.back()->setGeneralFade(true); if (element.second.has("audio")) mStaticVideoAudio = element.second.get("audio"); addChild(mStaticVideoComponents.back().get()); } else { mVideoComponents.push_back(std::make_unique()); mVideoComponents.back()->setDefaultZIndex(30.0f); mVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mVideoComponents.back()->getThemeImageTypes().size() != 0) mVideoComponents.back()->setScrollHide(true); addChild(mVideoComponents.back().get()); } } else if (element.second.type == "animation" && element.second.has("path") && !(element.second.has("visible") && !element.second.get("visible"))) { const std::string extension { Utils::FileSystem::getExtension(element.second.get("path"))}; if (extension == ".json") { mLottieAnimComponents.push_back(std::make_unique()); mLottieAnimComponents.back()->setDefaultZIndex(35.0f); mLottieAnimComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mLottieAnimComponents.back()->getMetadataElement()) mLottieAnimComponents.back()->setScrollHide(true); addChild(mLottieAnimComponents.back().get()); } else if (extension == ".gif") { mGIFAnimComponents.push_back(std::make_unique()); mGIFAnimComponents.back()->setDefaultZIndex(35.0f); mGIFAnimComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mGIFAnimComponents.back()->getMetadataElement()) mGIFAnimComponents.back()->setScrollHide(true); addChild(mGIFAnimComponents.back().get()); } else if (extension == ".") { LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme configuration, " "animation file extension is missing"; } else { LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme configuration, " "animation file extension defined as \"" << extension << "\""; } } else if (element.second.type == "badges" && !(element.second.has("visible") && !element.second.get("visible"))) { mBadgeComponents.push_back(std::make_unique()); mBadgeComponents.back()->setDefaultZIndex(35.0f); mBadgeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); mBadgeComponents.back()->setScrollHide(true); addChild(mBadgeComponents.back().get()); } else if (element.second.type == "text" && !(element.second.has("visible") && !element.second.get("visible"))) { // Set as container by default if metadata type is "description". bool container {false}; if (element.second.has("container")) { container = element.second.get("container"); if (element.second.has("containerType") && element.second.get("containerType") == "horizontal") container = false; } else if (element.second.has("metadata") && element.second.get("metadata") == "description") { container = true; } if (container) { mContainerComponents.push_back(std::make_unique()); mContainerComponents.back()->setDefaultZIndex(40.0f); mContainerTextComponents.push_back(std::make_unique()); mContainerTextComponents.back()->setDefaultZIndex(40.0f); mContainerComponents.back()->addChild(mContainerTextComponents.back().get()); mContainerComponents.back()->applyTheme(theme, "gamelist", element.first, POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); mContainerComponents.back()->setAutoScroll(true); mContainerTextComponents.back()->setSize( mContainerComponents.back()->getSize().x, 0.0f); mContainerTextComponents.back()->applyTheme( theme, "gamelist", element.first, ALL ^ POSITION ^ ORIGIN ^ Z_INDEX ^ ThemeFlags::SIZE ^ VISIBLE ^ ROTATION); if (mContainerTextComponents.back()->getThemeMetadata() != "") mContainerComponents.back()->setScrollHide(true); else if (mContainerTextComponents.back()->getMetadataElement()) mContainerComponents.back()->setScrollHide(true); addChild(mContainerComponents.back().get()); } else { mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setDefaultZIndex(40.0f); mTextComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); const std::string& metadata {mTextComponents.back()->getThemeMetadata()}; if (metadata != "" && metadata != "systemName" && metadata != "systemFullname" && metadata != "sourceSystemName" && metadata != "sourceSystemFullname") mTextComponents.back()->setScrollHide(true); else if (mTextComponents.back()->getMetadataElement()) mTextComponents.back()->setScrollHide(true); addChild(mTextComponents.back().get()); } } else if (element.second.type == "datetime" && !(element.second.has("visible") && !element.second.get("visible"))) { mDateTimeComponents.push_back(std::make_unique()); mDateTimeComponents.back()->setDefaultZIndex(40.0f); mDateTimeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); if (mDateTimeComponents.back()->getThemeMetadata() != "") mDateTimeComponents.back()->setScrollHide(true); addChild(mDateTimeComponents.back().get()); } else if (element.second.type == "gamelistinfo" && !(element.second.has("visible") && !element.second.get("visible"))) { mGamelistInfoComponents.push_back(std::make_unique()); mGamelistInfoComponents.back()->setDefaultZIndex(45.0f); mGamelistInfoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); addChild(mGamelistInfoComponents.back().get()); } else if (element.second.type == "rating" && !(element.second.has("visible") && !element.second.get("visible"))) { mRatingComponents.push_back(std::make_unique()); mRatingComponents.back()->setDefaultZIndex(45.0f); mRatingComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); mRatingComponents.back()->setScrollHide(true); mRatingComponents.back()->setOpacity(mRatingComponents.back()->getOpacity()); addChild(mRatingComponents.back().get()); } } mHelpStyle.applyTheme(mTheme, "gamelist"); } if (mPrimary == nullptr) { mTextList = std::make_unique>(); mPrimary = mTextList.get(); mPrimary->setCursorChangedCallback([&](const CursorState& state) { updateView(state); }); mPrimary->setDefaultZIndex(50.0f); mPrimary->setZIndex(50.0f); mPrimary->applyTheme(theme, "gamelist", "", ALL); addChild(mPrimary); } mSystemNameSuffix = mPrimary->getSystemNameSuffix(); populateList(mRoot->getChildrenListToDisplay(), mRoot); // Check whether the primary component uses the left and right buttons for its navigation. if (mCarousel != nullptr) { if (mCarousel->getType() == CarouselComponent::CarouselType::HORIZONTAL || mCarousel->getType() == CarouselComponent::CarouselType::HORIZONTAL_WHEEL) mLeftRightAvailable = false; } else if (mGrid != nullptr) { mLeftRightAvailable = false; } for (auto& video : mStaticVideoComponents) { if (video->hasStaticVideo()) video->setStaticVideo(); } sortChildren(); } void GamelistView::update(int deltaTime) { if (ViewController::getInstance()->getGameLaunchTriggered()) { for (auto& image : mImageComponents) { if (image->isAnimationPlaying(0)) image->finishAnimation(0); } } // We need to manually advance fade-in and fade-out animations since they will not get updated // via GuiComponent as these components override the update() function. for (auto& anim : mLottieAnimComponents) { if (anim->isAnimationPlaying(0)) anim->advanceAnimation(0, deltaTime); } for (auto& anim : mGIFAnimComponents) { if (anim->isAnimationPlaying(0)) anim->advanceAnimation(0, deltaTime); } updateChildren(deltaTime); } void GamelistView::render(const glm::mat4& parentTrans) { glm::mat4 trans {parentTrans * getTransform()}; // Make sure nothing renders outside our designated area. auto clipRectFunc = [this, trans]() { mRenderer->pushClipRect(glm::ivec2 {static_cast(std::round(trans[3].x)), static_cast(std::round(trans[3].y))}, glm::ivec2 {static_cast(std::round(mSize.x)), static_cast(std::round(mSize.y))}); }; clipRectFunc(); const ViewController::State viewState {ViewController::getInstance()->getState()}; bool stationaryApplicable {false}; auto renderChildCondFunc = [this, &viewState](int i, glm::mat4 trans) { bool renderChild {false}; if (!ViewController::getInstance()->isCameraMoving()) renderChild = true; else if (viewState.previouslyViewed == ViewController::ViewMode::NOTHING) renderChild = true; else if (viewState.viewing == viewState.previouslyViewed) renderChild = true; else if (static_cast(Settings::getInstance()->getInt( "TransitionsGamelistToSystem")) != ViewTransitionAnimation::SLIDE && viewState.viewing == ViewController::ViewMode::SYSTEM_SELECT) renderChild = true; if (renderChild) getChild(i)->render(trans); }; // If it's the startup animation, then don't apply stationary properties. if (viewState.previouslyViewed == ViewController::ViewMode::NOTHING) stationaryApplicable = false; // If it's a gamelist to gamelist transition and these animations are set to slide. if (static_cast(Settings::getInstance()->getInt( "TransitionsGamelistToGamelist")) == ViewTransitionAnimation::SLIDE && viewState.viewing == ViewController::ViewMode::GAMELIST && viewState.previouslyViewed == ViewController::ViewMode::GAMELIST) stationaryApplicable = true; // If it's a gamelist to system transition and these animations are set to slide. if (static_cast(Settings::getInstance()->getInt( "TransitionsGamelistToSystem")) == ViewTransitionAnimation::SLIDE && viewState.viewing == ViewController::ViewMode::SYSTEM_SELECT) stationaryApplicable = true; // If it's a system to gamelist transition and these animations are set to slide. if (static_cast(Settings::getInstance()->getInt( "TransitionsSystemToGamelist")) == ViewTransitionAnimation::SLIDE && viewState.previouslyViewed == ViewController::ViewMode::SYSTEM_SELECT) stationaryApplicable = true; for (unsigned int i {0}; i < getChildCount(); ++i) { bool childStationary {false}; if (stationaryApplicable) { if (getChild(i)->getStationary() == Stationary::NEVER) { childStationary = false; } else if (viewState.viewing == ViewController::ViewMode::GAMELIST && viewState.previouslyViewed == ViewController::ViewMode::GAMELIST && (getChild(i)->getStationary() == Stationary::WITHIN_VIEW || getChild(i)->getStationary() == Stationary::ALWAYS)) { childStationary = true; } else if (viewState.viewing == ViewController::ViewMode::SYSTEM_SELECT && (getChild(i)->getStationary() == Stationary::BETWEEN_VIEWS || getChild(i)->getStationary() == Stationary::ALWAYS)) { childStationary = true; } else if (viewState.previouslyViewed == ViewController::ViewMode::SYSTEM_SELECT && (getChild(i)->getStationary() == Stationary::BETWEEN_VIEWS || getChild(i)->getStationary() == Stationary::ALWAYS)) { childStationary = true; } } if (childStationary) { if (viewState.getSystem() != mRoot->getSystem()) continue; mRenderer->popClipRect(); if (getChild(i)->getRenderDuringTransitions()) getChild(i)->render(mRenderer->getIdentity()); else renderChildCondFunc(i, mRenderer->getIdentity()); clipRectFunc(); } else { if (getChild(i)->getRenderDuringTransitions()) getChild(i)->render(trans); else renderChildCondFunc(i, trans); } } mRenderer->popClipRect(); } std::vector GamelistView::getHelpPrompts() { std::vector prompts; if (Settings::getInstance()->getString("QuickSystemSelect") != "disabled") { if (getQuickSystemSelectLeftButton() == "leftshoulder") prompts.push_back(HelpPrompt("lr", _("system"))); else if (getQuickSystemSelectLeftButton() == "lefttrigger") prompts.push_back(HelpPrompt("ltrt", _("system"))); else if (getQuickSystemSelectLeftButton() == "left") prompts.push_back(HelpPrompt("left/right", _("system"))); } if (mRoot->getSystem()->getThemeFolder() == "custom-collections" && mCursorStack.empty() && ViewController::getInstance()->getState().viewing == ViewController::ViewMode::GAMELIST) prompts.push_back(HelpPrompt("a", _("select"))); else prompts.push_back(HelpPrompt("a", _("select"))); prompts.push_back(HelpPrompt("b", _("back"))); prompts.push_back(HelpPrompt("x", _("view media"))); if (!UIModeController::getInstance()->isUIModeKid()) prompts.push_back(HelpPrompt("back", _("options"))); if (mRoot->getSystem()->isGameSystem() && (Settings::getInstance()->getString("RandomEntryButton") == "games" || Settings::getInstance()->getString("RandomEntryButton") == "gamessystems")) prompts.push_back(HelpPrompt("thumbstickclick", _("random"))); if (mRoot->getSystem()->getThemeFolder() == "custom-collections" && !CollectionSystemsManager::getInstance()->isEditing() && mCursorStack.empty() && ViewController::getInstance()->getState().viewing == ViewController::ViewMode::GAMELIST) { prompts.push_back(HelpPrompt("y", _("jump to game"))); } else if (mRoot->getSystem()->isGameSystem() && (mRoot->getSystem()->getThemeFolder() != "custom-collections" || !mCursorStack.empty()) && !UIModeController::getInstance()->isUIModeKid() && !UIModeController::getInstance()->isUIModeKiosk() && (Settings::getInstance()->getBool("FavoritesAddButton") || CollectionSystemsManager::getInstance()->isEditing())) { std::string prompt {CollectionSystemsManager::getInstance()->getEditingCollection()}; if (prompt == "Favorites") prompt = _("Favorites"); if (prompt.length() > 24) prompt = prompt.substr(0, 22) + "..."; prompts.push_back(HelpPrompt("y", prompt)); } else if (mRoot->getSystem()->isGameSystem() && mRoot->getSystem()->getThemeFolder() == "custom-collections" && CollectionSystemsManager::getInstance()->isEditing()) { std::string prompt {CollectionSystemsManager::getInstance()->getEditingCollection()}; if (prompt.length() > 24) prompt = prompt.substr(0, 22) + "..."; prompts.push_back(HelpPrompt("y", prompt)); } return prompts; } void GamelistView::updateView(const CursorState& state) { bool loadedTexture {false}; if (mPrimary->isScrolling()) { onDemandTextureLoad(); loadedTexture = true; } FileData* file {(mPrimary->size() > 0 && state == CursorState::CURSOR_STOPPED) ? mPrimary->getSelected() : nullptr}; // If the game data has already been rendered to the view, then skip it this time. // This also happens when fast-scrolling. if (file == mLastUpdated) return; if (!loadedTexture) onDemandTextureLoad(); if (state == CursorState::CURSOR_STOPPED) mLastUpdated = file; bool hideMetaDataFields {false}; if (file) { // Always hide the metadata fields if browsing grouped custom collections. if (file->getSystem()->isCustomCollection() && file->getPath() == file->getSystem()->getName()) hideMetaDataFields = true; else hideMetaDataFields = (file->metadata.get("hidemetadata") == "true"); // Always hide the metadata fields for placeholders as well. if (file->getType() == PLACEHOLDER) { hideMetaDataFields = true; mLastUpdated = nullptr; } } // If we're scrolling, hide the metadata fields if the last game had this options set, // or if we're in the grouped custom collection view. if (state == CursorState::CURSOR_SCROLLING) { if ((mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true") || (mLastUpdated->getSystem()->isCustomCollection() && mLastUpdated->getPath() == mLastUpdated->getSystem()->getName())) hideMetaDataFields = true; } if (hideMetaDataFields) { for (auto& text : mTextComponents) { if (text->getMetadataElement() || (text->getThemeMetadata() != "" && text->getThemeMetadata() != "systemName" && text->getThemeMetadata() != "systemFullname" && text->getThemeMetadata() != "sourceSystemName" && text->getThemeMetadata() != "sourceSystemFullname")) text->setVisible(false); } for (auto& date : mDateTimeComponents) date->setVisible(false); for (auto& image : mImageComponents) { if (image->getMetadataElement()) image->setVisible(false); } for (auto& video : mStaticVideoComponents) { if (video->getMetadataElement()) video->setVisible(false); } for (auto& anim : mLottieAnimComponents) { if (anim->getMetadataElement()) anim->setVisible(false); } for (auto& anim : mGIFAnimComponents) { if (anim->getMetadataElement()) anim->setVisible(false); } for (auto& badge : mBadgeComponents) badge->setVisible(false); for (auto& rating : mRatingComponents) rating->setVisible(false); for (auto& cText : mContainerTextComponents) { if (cText->getThemeMetadata() != "description" || cText->getMetadataElement()) cText->setVisible(false); } } else { for (auto& text : mTextComponents) { if (text->getMetadataElement() || text->getThemeMetadata() != "") text->setVisible(true); } for (auto& image : mImageComponents) { if (image->getMetadataElement()) image->setVisible(true); } for (auto& video : mStaticVideoComponents) { if (video->getMetadataElement()) video->setVisible(true); } for (auto& anim : mLottieAnimComponents) { if (anim->getMetadataElement()) anim->setVisible(true); } for (auto& anim : mGIFAnimComponents) { if (anim->getMetadataElement()) anim->setVisible(true); } for (auto& date : mDateTimeComponents) date->setVisible(true); for (auto& badge : mBadgeComponents) badge->setVisible(true); for (auto& rating : mRatingComponents) rating->setVisible(true); for (auto& cText : mContainerTextComponents) { if (cText->getThemeMetadata() != "description" || cText->getMetadataElement()) cText->setVisible(true); } } bool fadingOut {false}; if (file == nullptr) { if (mVideoPlaying) { for (auto& video : mVideoComponents) { video->stopVideoPlayer(!mStaticVideoAudio); video->setVideo(""); if (!video->hasStartDelay()) video->setImageNoDefault(""); } } mVideoPlaying = false; fadingOut = true; } else { // If we're browsing a grouped custom collection, then update the folder metadata // which will generate a description of three random games and return a pointer to // the first of these so that we can display its game media. if (file->getSystem()->isCustomCollection() && file->getPath() == file->getSystem()->getName()) { mRandomGame = CollectionSystemsManager::getInstance()->updateCollectionFolderMetadata( file->getSystem()); if (mRandomGame) { for (auto& image : mImageComponents) setGameImage(mRandomGame, image.get()); for (auto& video : mVideoComponents) { setGameImage(mRandomGame, video.get()); video->stopVideoPlayer(!mStaticVideoAudio); if (video->hasStaticVideo()) video->setStaticVideo(); else if (!video->setVideo(mRandomGame->getVideoPath())) video->setDefaultVideo(); video->startVideoPlayer(); } } else { for (auto& image : mImageComponents) { if (image->getThemeImageTypes().size() != 0) image->setImage(""); } for (auto& video : mVideoComponents) { video->stopVideoPlayer(!mStaticVideoAudio); video->setImage(""); video->setVideo(""); if (video->hasStaticVideo()) { video->setStaticVideo(); } else { video->setDefaultVideo(); } video->startVideoPlayer(); } } } else { for (auto& image : mImageComponents) setGameImage(file, image.get()); for (auto& video : mVideoComponents) { setGameImage(file, video.get()); video->stopVideoPlayer(!mStaticVideoAudio); if (video->hasStaticVideo()) video->setStaticVideo(); else if (!video->setVideo(file->getVideoPath())) video->setDefaultVideo(); video->startVideoPlayer(); } } mVideoPlaying = true; // Populate the gamelistInfo field which shows an icon if a folder has been entered // as well as the game count for the entire system (total and favorites separately). // If a filter has been applied, then the number of filtered and total games replaces // the game counter. for (auto& gamelistInfo : mGamelistInfoComponents) { std::string gamelistInfoString; Alignment infoAlign {gamelistInfo->getHorizontalAlignment()}; if (mIsFolder && infoAlign == ALIGN_RIGHT) gamelistInfoString = ViewController::FOLDER_CHAR + " "; if (mIsFiltered) { if (mFilteredGameCountAll == mFilteredGameCount) gamelistInfoString.append(ViewController::FILTER_CHAR) .append(" ") .append(std::to_string(mFilteredGameCount)) .append(" / ") .append(std::to_string(mGameCount)); else gamelistInfoString.append(ViewController::FILTER_CHAR) .append(" ") .append(std::to_string(mFilteredGameCount)) .append(" + ") .append(std::to_string(mFilteredGameCountAll - mFilteredGameCount)) .append(" / ") .append(std::to_string(mGameCount)); } else { gamelistInfoString.append(ViewController::CONTROLLER_CHAR) .append(" ") .append(std::to_string(mGameCount)); if (!(file->getSystem()->isCollection() && file->getSystem()->getFullName() == "favorites")) gamelistInfoString.append(" ") .append(ViewController::FAVORITE_CHAR) .append(" ") .append(std::to_string(mFavoritesGameCount)); } if (mIsFolder && infoAlign != ALIGN_RIGHT) gamelistInfoString.append(" ").append(ViewController::FOLDER_CHAR); gamelistInfo->setValue(gamelistInfoString); } for (auto& image : mImageComponents) { if (image->getScrollFadeIn()) { auto func = [&image](float t) { image->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; image->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); } } for (auto& video : mVideoComponents) { if (video->getScrollFadeIn()) { auto func = [&video](float t) { video->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; video->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); } } for (auto& container : mContainerComponents) container->resetComponent(); // Reset horizontally scrolling text. for (auto& text : mTextComponents) text->resetComponent(); for (auto& rating : mRatingComponents) rating->setValue(file->metadata.get("rating")); // Populate the badge slots based on game metadata. std::vector badgeSlots; for (auto& badgeComponent : mBadgeComponents) { for (auto& badge : badgeComponent->getBadgeTypes()) { BadgeComponent::BadgeInfo badgeInfo; badgeInfo.badgeType = badge; if (badge == "collection" && CollectionSystemsManager::getInstance()->isEditing()) { if (CollectionSystemsManager::getInstance()->inCustomCollection( CollectionSystemsManager::getInstance()->getEditingCollection(), file)) { badgeSlots.emplace_back(badgeInfo); } } else if (badge == "folder") { if (file->getType() == FOLDER) { if (file->metadata.get("folderlink") != "") badgeInfo.folderLink = true; badgeSlots.emplace_back(badgeInfo); } } else if (badge == "controller") { if (file->metadata.get("controller") != "") { badgeInfo.gameController = file->metadata.get("controller"); badgeSlots.emplace_back(badgeInfo); } } else if (badge == "altemulator") { if (file->metadata.get(badge) != "") badgeSlots.emplace_back(badgeInfo); } else if (badge == "manual") { if (file->getManualPath() != "") badgeSlots.emplace_back(badgeInfo); } else { if (file->metadata.get(badge) == "true") badgeSlots.emplace_back(badgeInfo); } } badgeComponent->setBadges(badgeSlots); } for (auto& text : mTextComponents) { if (text->getThemeMetadata() == "name") text->setText(file->metadata.get("name")); } if (file->getType() == GAME) { if (!hideMetaDataFields) { for (auto& date : mDateTimeComponents) { if (date->getThemeMetadata() == "lastplayed") date->setValue(file->metadata.get("lastplayed")); else if (date->getThemeMetadata() == "playcount") date->setValue(file->metadata.get("playcount")); } } else if (file->getType() == FOLDER) { if (!hideMetaDataFields) { for (auto& date : mDateTimeComponents) { if (date->getThemeMetadata() == "lastplayed") { date->setValue(file->metadata.get("lastplayed")); date->setVisible(false); date->setVisible(false); } } } } } std::string metadata; auto getMetadataValue = [&file, &metadata]() -> std::string { #if defined(GETTEXT_DUMMY_ENTRIES) _p("theme", "all"); _p("theme", "all games"); _p("theme", "recent"); _p("theme", "last played"); _p("theme", "favorites"); _p("theme", "collections"); -p("theme", "unknown"); #endif if (metadata == "name") return file->metadata.get("name"); else if (metadata == "description") return file->metadata.get("desc"); else if (metadata == "developer") return (file->metadata.get("developer") == "unknown" ? _p("theme", "unknown") : file->metadata.get("developer")); else if (metadata == "publisher") return (file->metadata.get("publisher") == "unknown" ? _p("theme", "unknown") : file->metadata.get("publisher")); else if (metadata == "genre") return (file->metadata.get("genre") == "unknown" ? _p("theme", "unknown") : file->metadata.get("genre")); else if (metadata == "players") return (file->metadata.get("players") == "unknown" ? _p("theme", "unknown") : file->metadata.get("players")); else if (metadata == "favorite") return file->metadata.get("favorite") == "true" ? _p("theme", "yes") : _p("theme", "no"); else if (metadata == "completed") return file->metadata.get("completed") == "true" ? _p("theme", "yes") : _p("theme", "no"); else if (metadata == "kidgame") return file->metadata.get("kidgame") == "true" ? _p("theme", "yes") : _p("theme", "no"); else if (metadata == "broken") return file->metadata.get("broken") == "true" ? _p("theme", "yes") : _p("theme", "no"); else if (metadata == "manual") return file->getManualPath() != "" ? _p("theme", "yes") : _p("theme", "no"); else if (metadata == "playcount") return file->metadata.get("playcount"); else if (metadata == "altemulator") return file->metadata.get("altemulator"); else if (metadata == "emulator") return (file->getType() == FOLDER || file->getType() == PLACEHOLDER) ? "" : (file->metadata.get("altemulator") != "" ? file->metadata.get("altemulator") : (file->getSourceSystem()->getAlternativeEmulator() != "" ? file->getSourceSystem()->getAlternativeEmulator() : file->getSourceSystem() ->getSystemEnvData() ->mLaunchCommands.front() .second)); else if (metadata == "physicalName") return file->getType() == PLACEHOLDER ? "" : Utils::FileSystem::getStem(file->getFileName()); else if (metadata == "physicalNameExtension") return file->getType() == PLACEHOLDER ? "" : file->getFileName(); else if (metadata == "systemName" && file->getSystem()->isCollection() && !file->getSystem()->isCustomCollection()) return _p("theme", file->getSystem()->getName().c_str()); else if (metadata == "systemName") return file->getSystem()->getName(); else if (metadata == "systemFullname" && file->getSystem()->isCollection() && !file->getSystem()->isCustomCollection()) return _p("theme", file->getSystem()->getFullName().c_str()); else if (metadata == "systemFullname") return file->getSystem()->getFullName(); else if (metadata == "sourceSystemName") return file->getSourceFileData()->getSystem()->getName(); else if (metadata == "sourceSystemFullname") return file->getSourceFileData()->getSystem()->getFullName(); else return metadata; }; for (auto& text : mContainerTextComponents) { metadata = text->getThemeMetadata(); if (metadata == "") continue; if (metadata == "rating") { text->setValue(RatingComponent::getRatingValue(file->metadata.get("rating"))); continue; } else if (metadata == "controller") { std::string controller { BadgeComponent::getDisplayName(file->metadata.get("controller"))}; text->setValue(controller == "unknown" ? "" : controller); continue; } if (metadata == "name" && file->getSystem()->isCollection() && text->getSystemNameSuffix()) { const LetterCase letterCase {text->getLetterCaseSystemNameSuffix()}; std::string suffix {" ["}; if (letterCase == LetterCase::UPPERCASE) suffix.append( Utils::String::toUpper(file->getSourceFileData()->getSystem()->getName())); else if (letterCase == LetterCase::CAPITALIZE) suffix.append(Utils::String::toCapitalized( file->getSourceFileData()->getSystem()->getName())); else suffix.append(file->getSourceFileData()->getSystem()->getName()); suffix.append("]"); text->setValue(getMetadataValue() + suffix); } else { text->setValue(getMetadataValue()); } } for (auto& text : mTextComponents) { metadata = text->getThemeMetadata(); if (metadata == "") continue; if ((file->getSystem()->isCustomCollection() && file->getPath() == file->getSystem()->getName()) && (metadata == "systemName" || metadata == "systemFullname" || metadata == "sourceSystemName" || metadata == "sourceSystemFullname")) { text->setValue(text->getDefaultValue()); continue; } if (metadata == "rating") { text->setValue(RatingComponent::getRatingValue(file->metadata.get("rating"))); continue; } else if (metadata == "controller") { std::string controller = BadgeComponent::getDisplayName(file->metadata.get("controller")); text->setValue(controller == "unknown" ? "" : controller); continue; } if (metadata == "name" && file->getSystem()->isCollection() && text->getSystemNameSuffix()) { const LetterCase letterCase {text->getLetterCaseSystemNameSuffix()}; std::string suffix {" ["}; if (letterCase == LetterCase::UPPERCASE) suffix.append( Utils::String::toUpper(file->getSourceFileData()->getSystem()->getName())); else if (letterCase == LetterCase::CAPITALIZE) suffix.append(Utils::String::toCapitalized( file->getSourceFileData()->getSystem()->getName())); else suffix.append(file->getSourceFileData()->getSystem()->getName()); suffix.append("]"); text->setValue(getMetadataValue() + suffix); } else { text->setValue(getMetadataValue()); } } for (auto& date : mDateTimeComponents) { std::string metadata {date->getThemeMetadata()}; if (metadata == "") continue; if (metadata == "releasedate") { date->setValue(file->metadata.get("releasedate")); } else if (metadata == "lastplayed") { date->setValue(file->metadata.get("lastplayed")); } else { date->setValue("19700101T000000"); } } } std::vector comps; for (auto& text : mTextComponents) { if (text->getScrollHide()) comps.emplace_back(text.get()); } for (auto& date : mDateTimeComponents) { if (date->getScrollHide()) comps.emplace_back(date.get()); } for (auto& image : mImageComponents) { if (image->getScrollHide()) comps.emplace_back(image.get()); } for (auto& video : mStaticVideoComponents) { if (video->getScrollHide()) comps.emplace_back(video.get()); } for (auto& video : mVideoComponents) { if (video->getScrollHide()) comps.emplace_back(video.get()); } for (auto& anim : mLottieAnimComponents) { if (anim->getScrollHide()) comps.emplace_back(anim.get()); } for (auto& anim : mGIFAnimComponents) { if (anim->getScrollHide()) comps.emplace_back(anim.get()); } for (auto& badge : mBadgeComponents) { if (badge->getScrollHide()) comps.emplace_back(badge.get()); } for (auto& rating : mRatingComponents) { if (rating->getScrollHide()) comps.emplace_back(rating.get()); } for (auto& container : mContainerComponents) { if (container->getScrollHide()) comps.emplace_back(container.get()); } for (auto it = comps.cbegin(); it != comps.cend(); ++it) { GuiComponent* comp {*it}; if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) || (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0.0f : comp->getColorOpacity()))) { auto func = [comp](float t) { comp->setOpacity(glm::mix(0.0f, 1.0f, t)); }; comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut); } } if (state == CursorState::CURSOR_SCROLLING) mLastUpdated = nullptr; } void GamelistView::setGameImage(FileData* file, GuiComponent* comp) { std::string path; for (auto& imageType : comp->getThemeImageTypes()) { if (imageType == "image") { path = file->getImagePath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "miximage") { path = file->getMiximagePath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "marquee") { path = file->getMarqueePath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "screenshot") { path = file->getScreenshotPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "titlescreen") { path = file->getTitleScreenPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "cover") { path = file->getCoverPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "backcover") { path = file->getBackCoverPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "3dbox") { path = file->get3DBoxPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "physicalmedia") { path = file->getPhysicalMediaPath(); if (path != "") { comp->setImage(path); return; } } else if (imageType == "fanart") { path = file->getFanArtPath(); if (path != "") { comp->setImage(path); return; } } } // This is needed so the default image is set if no game media was found. 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()); }