mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-27 00:25:38 +00:00
735 lines
29 KiB
C++
735 lines
29 KiB
C++
// SPDX-License-Identifier: MIT
|
|
//
|
|
// ES-DE Frontend
|
|
// Screensaver.cpp
|
|
//
|
|
// Screensaver, supporting the following types:
|
|
// Dim, black, slideshow, video.
|
|
//
|
|
|
|
#include "Screensaver.h"
|
|
|
|
#include "FileData.h"
|
|
#include "Log.h"
|
|
#include "SystemData.h"
|
|
#include "UIModeController.h"
|
|
#include "components/VideoFFmpegComponent.h"
|
|
#include "resources/Font.h"
|
|
#include "utils/FileSystemUtil.h"
|
|
#include "utils/StringUtil.h"
|
|
#include "views/GamelistView.h"
|
|
#include "views/ViewController.h"
|
|
|
|
#include <random>
|
|
#include <time.h>
|
|
|
|
#if defined(_WIN64)
|
|
#include <cstring>
|
|
#endif
|
|
|
|
#define IMAGES_FADE_IN_TIME 450.0f
|
|
|
|
Screensaver::Screensaver()
|
|
: mRenderer {Renderer::getInstance()}
|
|
, mWindow {Window::getInstance()}
|
|
, mImageScreensaver {nullptr}
|
|
, mVideoScreensaver {nullptr}
|
|
, mCurrentGame {nullptr}
|
|
, mPreviousGame {nullptr}
|
|
, mTimer {0}
|
|
, mMediaSwapTime {0}
|
|
, mScreensaverActive {false}
|
|
, mTriggerNextGame {false}
|
|
, mHasMediaFiles {false}
|
|
, mFallbackScreensaver {false}
|
|
, mOpacity {0.0f}
|
|
, mDimValue {1.0}
|
|
, mRectangleFadeIn {50}
|
|
, mTextFadeIn {0}
|
|
, mSaturationAmount {1.0}
|
|
{
|
|
mWindow->setScreensaver(this);
|
|
}
|
|
|
|
void Screensaver::startScreensaver(bool generateMediaList)
|
|
{
|
|
ViewController::getInstance()->pauseViewVideos();
|
|
|
|
mScreensaverType = Settings::getInstance()->getString("ScreensaverType");
|
|
// In case there is an invalid entry in the es_settings.xml file.
|
|
if (mScreensaverType != "dim" && mScreensaverType != "black" &&
|
|
mScreensaverType != "slideshow" && mScreensaverType != "video") {
|
|
mScreensaverType = "dim";
|
|
}
|
|
std::string path;
|
|
mScreensaverActive = true;
|
|
mHasMediaFiles = false;
|
|
mFallbackScreensaver = false;
|
|
mOpacity = 0.0f;
|
|
|
|
// Keep a reference to the default fonts, so they don't keep getting destroyed/recreated.
|
|
if (mGameOverlayFont.empty()) {
|
|
mGameOverlayFont.push_back(Font::get(FONT_SIZE_SMALL));
|
|
mGameOverlayFont.push_back(Font::get(FONT_SIZE_MEDIUM));
|
|
mGameOverlayFont.push_back(Font::get(FONT_SIZE_LARGE));
|
|
}
|
|
|
|
// Set mPreviousGame which will be used to avoid showing the same game again during
|
|
// the random selection.
|
|
if ((mScreensaverType == "slideshow" || mScreensaverType == "video") && mCurrentGame != nullptr)
|
|
mPreviousGame = mCurrentGame;
|
|
|
|
if (mScreensaverType == "slideshow") {
|
|
if (generateMediaList) {
|
|
mImageFiles.clear();
|
|
mFilesInventory.clear();
|
|
mImageCustomFiles.clear();
|
|
mCustomFilesInventory.clear();
|
|
}
|
|
|
|
mMediaSwapTime = Settings::getInstance()->getInt("ScreensaverSwapImageTimeout");
|
|
|
|
// Load a random image.
|
|
if (Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")) {
|
|
if (generateMediaList)
|
|
generateCustomImageList();
|
|
pickRandomCustomImage(path);
|
|
|
|
// We've cycled through all games, so start from the beginning again.
|
|
if (mImageCustomFiles.size() == 0 && mCustomFilesInventory.size() > 0)
|
|
mImageCustomFiles.insert(mImageCustomFiles.begin(), mCustomFilesInventory.begin(),
|
|
mCustomFilesInventory.end());
|
|
|
|
if (mImageCustomFiles.size() > 0)
|
|
mHasMediaFiles = true;
|
|
// Custom images are not tied to the game list.
|
|
mCurrentGame = nullptr;
|
|
}
|
|
else {
|
|
if (generateMediaList)
|
|
generateImageList();
|
|
pickRandomImage(path);
|
|
}
|
|
|
|
// We've cycled through all games, so start from the beginning again.
|
|
if (mImageFiles.size() == 0 && mFilesInventory.size() > 0)
|
|
mImageFiles.insert(mImageFiles.begin(), mFilesInventory.begin(), mFilesInventory.end());
|
|
|
|
if (mImageFiles.size() > 0)
|
|
mHasMediaFiles = true;
|
|
|
|
// Don't attempt to render the screensaver if there are no images available, but
|
|
// do flag it as running. This way render() will fade to a black screen, i.e. it
|
|
// will activate the 'Black' screensaver type.
|
|
if (mImageFiles.size() > 0 || mImageCustomFiles.size() > 0) {
|
|
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo"))
|
|
generateOverlayInfo();
|
|
|
|
if (!mImageScreensaver)
|
|
mImageScreensaver = std::make_unique<ImageComponent>(false, false);
|
|
|
|
mTimer = 0;
|
|
|
|
mImageScreensaver->setImage(path);
|
|
mImageScreensaver->setOrigin(0.5f, 0.5f);
|
|
mImageScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f,
|
|
Renderer::getScreenHeight() / 2.0f);
|
|
|
|
if (Settings::getInstance()->getBool("ScreensaverStretchImages"))
|
|
mImageScreensaver->setResize(Renderer::getScreenWidth(),
|
|
Renderer::getScreenHeight());
|
|
else
|
|
mImageScreensaver->setMaxSize(Renderer::getScreenWidth(),
|
|
Renderer::getScreenHeight());
|
|
}
|
|
mTimer = 0;
|
|
return;
|
|
}
|
|
else if (!mVideoScreensaver && (mScreensaverType == "video")) {
|
|
if (generateMediaList) {
|
|
mVideoFiles.clear();
|
|
mFilesInventory.clear();
|
|
}
|
|
|
|
mMediaSwapTime = Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout");
|
|
|
|
// Load a random video.
|
|
if (generateMediaList)
|
|
generateVideoList();
|
|
pickRandomVideo(path);
|
|
|
|
// We've cycled through all games, so start from the beginning again.
|
|
if (mVideoFiles.size() == 0 && mFilesInventory.size() > 0)
|
|
mVideoFiles.insert(mVideoFiles.begin(), mFilesInventory.begin(), mFilesInventory.end());
|
|
|
|
if (mVideoFiles.size() > 0)
|
|
mHasMediaFiles = true;
|
|
|
|
if (!path.empty() && Utils::FileSystem::exists(path)) {
|
|
if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo"))
|
|
generateOverlayInfo();
|
|
|
|
mVideoScreensaver = std::make_unique<VideoFFmpegComponent>();
|
|
mVideoScreensaver->setOrigin(0.5f, 0.5f);
|
|
mVideoScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f,
|
|
Renderer::getScreenHeight() / 2.0f);
|
|
|
|
if (Settings::getInstance()->getBool("ScreensaverStretchVideos"))
|
|
mVideoScreensaver->setResize(Renderer::getScreenWidth(),
|
|
Renderer::getScreenHeight());
|
|
else
|
|
mVideoScreensaver->setMaxSize(Renderer::getScreenWidth(),
|
|
Renderer::getScreenHeight());
|
|
|
|
mVideoScreensaver->setVideo(path);
|
|
mVideoScreensaver->setScreensaverMode(true);
|
|
mVideoScreensaver->startVideoPlayer();
|
|
mTimer = 0;
|
|
return;
|
|
}
|
|
}
|
|
// No videos or images, just use a standard screensaver.
|
|
mCurrentGame = nullptr;
|
|
}
|
|
|
|
void Screensaver::stopScreensaver()
|
|
{
|
|
mImageScreensaver.reset();
|
|
mVideoScreensaver.reset();
|
|
|
|
mScreensaverActive = false;
|
|
mDimValue = 1.0f;
|
|
mRectangleFadeIn = 50;
|
|
mTextFadeIn = 0;
|
|
mSaturationAmount = 1.0f;
|
|
|
|
if (mGameOverlay)
|
|
mGameOverlay.reset();
|
|
|
|
ViewController::getInstance()->startViewVideos();
|
|
}
|
|
|
|
void Screensaver::nextGame()
|
|
{
|
|
stopScreensaver();
|
|
startScreensaver(false);
|
|
}
|
|
|
|
void Screensaver::launchGame()
|
|
{
|
|
if (mCurrentGame != nullptr) {
|
|
// If the game is inside a folder where a folder link entry is present, then jump to
|
|
// that folder instead of to the actual game file. Also check the complete hierarchy in
|
|
// case folder link entries are set on multiple levels.
|
|
FileData* entry {mCurrentGame};
|
|
FileData* selectGame {mCurrentGame};
|
|
FileData* launchFolder {nullptr};
|
|
|
|
while (entry != nullptr) {
|
|
entry = entry->getParent();
|
|
if (entry != nullptr && entry->metadata.get("folderlink") != "")
|
|
launchFolder = entry;
|
|
}
|
|
|
|
if (launchFolder != nullptr)
|
|
selectGame = launchFolder;
|
|
|
|
// Launching game
|
|
ViewController::getInstance()->triggerGameLaunch(mCurrentGame);
|
|
ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem());
|
|
GamelistView* view {
|
|
ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get()};
|
|
view->setCursor(selectGame);
|
|
view->stopListScrolling();
|
|
ViewController::getInstance()->cancelViewTransitions();
|
|
ViewController::getInstance()->pauseViewVideos();
|
|
}
|
|
}
|
|
|
|
void Screensaver::goToGame()
|
|
{
|
|
if (mCurrentGame != nullptr) {
|
|
FileData* entry {mCurrentGame};
|
|
FileData* launchFolder {nullptr};
|
|
|
|
while (entry != nullptr) {
|
|
entry = entry->getParent();
|
|
if (entry != nullptr && entry->metadata.get("folderlink") != "")
|
|
launchFolder = entry;
|
|
}
|
|
|
|
if (launchFolder != nullptr)
|
|
mCurrentGame = launchFolder;
|
|
|
|
// Go to the game in the gamelist view, but don't launch it.
|
|
ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem());
|
|
GamelistView* view {
|
|
ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get()};
|
|
view->setCursor(mCurrentGame);
|
|
view->stopListScrolling();
|
|
ViewController::getInstance()->cancelViewTransitions();
|
|
}
|
|
}
|
|
|
|
void Screensaver::renderScreensaver()
|
|
{
|
|
glm::mat4 trans {Renderer::getIdentity()};
|
|
mRenderer->setMatrix(trans);
|
|
|
|
if (mVideoScreensaver && mScreensaverType == "video") {
|
|
// Render a black background below the video.
|
|
mRenderer->drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
|
|
0x000000FF, 0x000000FF);
|
|
mVideoScreensaver->render(trans);
|
|
}
|
|
else if (mImageScreensaver && mScreensaverType == "slideshow") {
|
|
// Render a black background below the image.
|
|
mRenderer->drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
|
|
0x000000FF, 0x000000FF);
|
|
// Leave a small gap without rendering during fade-in.
|
|
if (mOpacity > 0.5f) {
|
|
mImageScreensaver->setOpacity(mOpacity);
|
|
mImageScreensaver->render(trans);
|
|
}
|
|
}
|
|
|
|
if (mScreensaverType == "slideshow") {
|
|
if (mHasMediaFiles) {
|
|
if (Settings::getInstance()->getBool("ScreensaverSlideshowScanlines"))
|
|
mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES);
|
|
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") &&
|
|
!Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages") &&
|
|
mGameOverlay) {
|
|
mRenderer->setMatrix(mRenderer->getIdentity());
|
|
if (mGameOverlayRectangleCoords.size() == 4) {
|
|
mRenderer->drawRect(
|
|
mGameOverlayRectangleCoords[0], mGameOverlayRectangleCoords[1],
|
|
mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[3],
|
|
0x00000000 | mRectangleFadeIn, 0x00000000 | mRectangleFadeIn);
|
|
}
|
|
mRectangleFadeIn = glm::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn / 20, 0, 170);
|
|
|
|
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
|
|
if (mTextFadeIn > 50)
|
|
mGameOverlayFont.at(0)->renderTextCache(mGameOverlay.get());
|
|
if (mTextFadeIn < 255)
|
|
mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255);
|
|
}
|
|
}
|
|
else {
|
|
mFallbackScreensaver = true;
|
|
}
|
|
}
|
|
else if (mScreensaverType == "video") {
|
|
if (mHasMediaFiles) {
|
|
Renderer::postProcessingParams videoParameters;
|
|
unsigned int shaders {0};
|
|
if (Settings::getInstance()->getBool("ScreensaverVideoScanlines"))
|
|
shaders = Renderer::Shader::SCANLINES;
|
|
if (Settings::getInstance()->getBool("ScreensaverVideoBlur")) {
|
|
if (mRenderer->getScreenRotation() == 90 || mRenderer->getScreenRotation() == 270)
|
|
shaders |= Renderer::Shader::BLUR_VERTICAL;
|
|
else
|
|
shaders |= Renderer::Shader::BLUR_HORIZONTAL;
|
|
}
|
|
|
|
// We run two passes to make the blur smoother.
|
|
videoParameters.blurPasses = 2;
|
|
videoParameters.blurStrength = 1.35f;
|
|
|
|
if (shaders != 0)
|
|
mRenderer->shaderPostprocessing(shaders, videoParameters);
|
|
|
|
if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo") && mGameOverlay) {
|
|
mRenderer->setMatrix(mRenderer->getIdentity());
|
|
if (mGameOverlayRectangleCoords.size() == 4) {
|
|
mRenderer->drawRect(
|
|
mGameOverlayRectangleCoords[0], mGameOverlayRectangleCoords[1],
|
|
mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[3],
|
|
0x00000000 | mRectangleFadeIn, 0x00000000 | mRectangleFadeIn);
|
|
}
|
|
mRectangleFadeIn = glm::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn / 20, 0, 170);
|
|
|
|
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
|
|
if (mTextFadeIn > 50)
|
|
mGameOverlayFont.at(0)->renderTextCache(mGameOverlay.get());
|
|
if (mTextFadeIn < 255)
|
|
mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255);
|
|
}
|
|
}
|
|
else {
|
|
mFallbackScreensaver = true;
|
|
}
|
|
}
|
|
|
|
if (mFallbackScreensaver || mScreensaverType == "dim") {
|
|
Renderer::postProcessingParams dimParameters;
|
|
dimParameters.dimming = mDimValue;
|
|
dimParameters.saturation = mSaturationAmount;
|
|
mRenderer->shaderPostprocessing(Renderer::Shader::CORE, dimParameters);
|
|
if (mDimValue > 0.4)
|
|
mDimValue = glm::clamp(mDimValue - 0.021f, 0.4f, 1.0f);
|
|
if (mSaturationAmount > 0.0)
|
|
mSaturationAmount = glm::clamp(mSaturationAmount - 0.035f, 0.0f, 1.0f);
|
|
}
|
|
else if (mScreensaverType == "black") {
|
|
Renderer::postProcessingParams blackParameters;
|
|
blackParameters.dimming = mDimValue;
|
|
mRenderer->shaderPostprocessing(Renderer::Shader::CORE, blackParameters);
|
|
if (mDimValue > 0.0)
|
|
mDimValue = glm::clamp(mDimValue - 0.045f, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
void Screensaver::update(int deltaTime)
|
|
{
|
|
// Update the timer that swaps the media, unless the swap time is set to 0 (only
|
|
// applicable for the video screensaver). This means that videos play to the end,
|
|
// at which point the video player will trigger a skip to the next game.
|
|
if (mMediaSwapTime != 0) {
|
|
mTimer += deltaTime;
|
|
if (mTimer > mMediaSwapTime)
|
|
nextGame();
|
|
}
|
|
if (mTriggerNextGame) {
|
|
mTriggerNextGame = false;
|
|
nextGame();
|
|
}
|
|
|
|
// Fade-in for the video screensaver is handled in VideoComponent.
|
|
if (mImageScreensaver && mOpacity < 1.0f) {
|
|
mOpacity += static_cast<float>(deltaTime) / IMAGES_FADE_IN_TIME;
|
|
if (mOpacity > 1.0f)
|
|
mOpacity = 1.0f;
|
|
}
|
|
|
|
if (mVideoScreensaver)
|
|
mVideoScreensaver->update(deltaTime);
|
|
}
|
|
|
|
void Screensaver::generateImageList()
|
|
{
|
|
const bool favoritesOnly {
|
|
Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites")};
|
|
|
|
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
|
|
it != SystemData::sSystemVector.cend(); ++it) {
|
|
// We only want nodes from game systems that are not collections.
|
|
if (!(*it)->isGameSystem() || (*it)->isCollection())
|
|
continue;
|
|
|
|
// This method of building an inventory of all image files isn't pretty, but to use the
|
|
// FileData::getImagePath() function leads to unacceptable performance issues on some
|
|
// platforms like Android that offer very poor disk I/O performance. To instead list
|
|
// all files recursively is much faster as this avoids stat() function calls which are
|
|
// very expensive on such problematic platforms.
|
|
const std::string mediaDirMiximages {
|
|
FileData::getMediaDirectory() + (*it)->getRootFolder()->getSystemName() + "/miximages"};
|
|
const std::string mediaDirScreenshots {FileData::getMediaDirectory() +
|
|
(*it)->getRootFolder()->getSystemName() +
|
|
"/screenshots"};
|
|
const std::string mediaDirTitlescreens {FileData::getMediaDirectory() +
|
|
(*it)->getRootFolder()->getSystemName() +
|
|
"/titlescreens"};
|
|
const std::string mediaDirCovers {FileData::getMediaDirectory() +
|
|
(*it)->getRootFolder()->getSystemName() + "/covers"};
|
|
const Utils::FileSystem::StringList dirContentMiximages {
|
|
Utils::FileSystem::getDirContent(mediaDirMiximages, true)};
|
|
const Utils::FileSystem::StringList dirContentScreenshots {
|
|
Utils::FileSystem::getDirContent(mediaDirScreenshots, true)};
|
|
const Utils::FileSystem::StringList dirContentTitlescreens {
|
|
Utils::FileSystem::getDirContent(mediaDirTitlescreens, true)};
|
|
const Utils::FileSystem::StringList dirContentCovers {
|
|
Utils::FileSystem::getDirContent(mediaDirCovers, true)};
|
|
|
|
std::string subFolders;
|
|
|
|
std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)};
|
|
for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) {
|
|
// Only include games suitable for children if we're in Kid UI mode.
|
|
if (UIModeController::getInstance()->isUIModeKid() &&
|
|
(*it2)->metadata.get("kidgame") != "true")
|
|
continue;
|
|
if (favoritesOnly && (*it2)->metadata.get("favorite") != "true")
|
|
continue;
|
|
|
|
subFolders = Utils::String::replace(Utils::FileSystem::getParent((*it2)->getPath()),
|
|
(*it)->getStartPath(), "");
|
|
const std::string gamePath {subFolders + "/" + (*it2)->getDisplayName()};
|
|
|
|
for (auto& extension : FileData::sImageExtensions) {
|
|
if (std::find(dirContentMiximages.cbegin(), dirContentMiximages.cend(),
|
|
mediaDirMiximages + gamePath + extension) !=
|
|
dirContentMiximages.cend()) {
|
|
mImageFiles.push_back((*it2));
|
|
break;
|
|
}
|
|
if (std::find(dirContentScreenshots.cbegin(), dirContentScreenshots.cend(),
|
|
mediaDirScreenshots + gamePath + extension) !=
|
|
dirContentScreenshots.cend()) {
|
|
mImageFiles.push_back((*it2));
|
|
break;
|
|
}
|
|
if (std::find(dirContentTitlescreens.cbegin(), dirContentTitlescreens.cend(),
|
|
mediaDirTitlescreens + gamePath + extension) !=
|
|
dirContentTitlescreens.cend()) {
|
|
mImageFiles.push_back((*it2));
|
|
break;
|
|
}
|
|
if (std::find(dirContentCovers.cbegin(), dirContentCovers.cend(),
|
|
mediaDirCovers + gamePath + extension) != dirContentCovers.cend()) {
|
|
mImageFiles.push_back((*it2));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mFilesInventory.insert(mFilesInventory.begin(), mImageFiles.begin(), mImageFiles.end());
|
|
}
|
|
|
|
void Screensaver::generateVideoList()
|
|
{
|
|
const bool favoritesOnly {Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")};
|
|
|
|
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
|
|
it != SystemData::sSystemVector.cend(); ++it) {
|
|
// We only want nodes from game systems that are not collections.
|
|
if (!(*it)->isGameSystem() || (*it)->isCollection())
|
|
continue;
|
|
|
|
// This method of building an inventory of all video files isn't pretty, but to use the
|
|
// FileData::getVideoPath() function leads to unacceptable performance issues on some
|
|
// platforms like Android that offer very poor disk I/O performance. To instead list
|
|
// all files recursively is much faster as this avoids stat() function calls which are
|
|
// very expensive on such problematic platforms.
|
|
const std::string mediaDir {FileData::getMediaDirectory() +
|
|
(*it)->getRootFolder()->getSystemName() + "/videos"};
|
|
const Utils::FileSystem::StringList dirContent {
|
|
Utils::FileSystem::getDirContent(mediaDir, true)};
|
|
|
|
std::string subFolders;
|
|
|
|
std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)};
|
|
for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) {
|
|
// Only include games suitable for children if we're in Kid UI mode.
|
|
if (UIModeController::getInstance()->isUIModeKid() &&
|
|
(*it2)->metadata.get("kidgame") != "true")
|
|
continue;
|
|
if (favoritesOnly && (*it2)->metadata.get("favorite") != "true")
|
|
continue;
|
|
|
|
subFolders = Utils::String::replace(Utils::FileSystem::getParent((*it2)->getPath()),
|
|
(*it)->getStartPath(), "");
|
|
const std::string gamePath {subFolders + "/" + (*it2)->getDisplayName()};
|
|
|
|
for (auto& extension : FileData::sVideoExtensions) {
|
|
if (std::find(dirContent.cbegin(), dirContent.cend(),
|
|
mediaDir + gamePath + extension) != dirContent.cend()) {
|
|
mVideoFiles.push_back((*it2));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mFilesInventory.insert(mFilesInventory.begin(), mVideoFiles.begin(), mVideoFiles.end());
|
|
}
|
|
|
|
void Screensaver::generateCustomImageList()
|
|
{
|
|
std::string imageDir {Utils::FileSystem::expandHomePath(
|
|
Settings::getInstance()->getString("ScreensaverSlideshowCustomDir"))};
|
|
|
|
if (imageDir.empty())
|
|
imageDir = Utils::FileSystem::getAppDataDirectory() + "/screensavers/custom_slideshow";
|
|
|
|
// This makes it possible to set the custom image directory relative to the ES-DE binary
|
|
// directory or the ROM directory.
|
|
imageDir = Utils::String::replace(imageDir, "%ESPATH%", Utils::FileSystem::getExePath());
|
|
imageDir = Utils::String::replace(imageDir, "%ROMPATH%", FileData::getROMDirectory());
|
|
|
|
if (imageDir != "" && Utils::FileSystem::isDirectory(imageDir)) {
|
|
const std::vector<std::string> extList {".jpg", ".JPG", ".png", ".PNG", ".gif",
|
|
".GIF", ".webp", ".WEBP", ".svg", ".SVG"};
|
|
|
|
Utils::FileSystem::StringList dirContent {Utils::FileSystem::getDirContent(
|
|
imageDir, Settings::getInstance()->getBool("ScreensaverSlideshowRecurse"))};
|
|
|
|
for (auto it = dirContent.begin(); it != dirContent.end(); ++it) {
|
|
if (Utils::FileSystem::isRegularFile(*it)) {
|
|
if (std::find(extList.cbegin(), extList.cend(),
|
|
Utils::FileSystem::getExtension(*it)) != extList.cend())
|
|
mImageCustomFiles.push_back(*it);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
LOG(LogWarning) << "Custom screensaver image directory \"" << imageDir
|
|
<< "\" does not exist";
|
|
}
|
|
|
|
mCustomFilesInventory.insert(mCustomFilesInventory.begin(), mImageCustomFiles.begin(),
|
|
mImageCustomFiles.end());
|
|
}
|
|
|
|
void Screensaver::pickRandomImage(std::string& path)
|
|
{
|
|
mCurrentGame = nullptr;
|
|
|
|
if (mImageFiles.size() == 0)
|
|
return;
|
|
|
|
if (mImageFiles.size() == 1) {
|
|
mPreviousGame = nullptr;
|
|
mCurrentGame = mImageFiles.front();
|
|
path = mImageFiles.front()->getImagePath();
|
|
mGameName = mImageFiles.front()->getName();
|
|
mSystemName = mImageFiles.front()->getSystem()->getFullName();
|
|
mCurrentGame = mImageFiles.front();
|
|
mImageFiles.clear();
|
|
return;
|
|
}
|
|
|
|
unsigned int index;
|
|
do {
|
|
// Get a random number in range.
|
|
std::random_device randDev;
|
|
// Mersenne Twister pseudorandom number generator.
|
|
std::mt19937 engine {randDev()};
|
|
std::uniform_int_distribution<int> uniform_dist {0,
|
|
static_cast<int>(mImageFiles.size()) - 1};
|
|
index = uniform_dist(engine);
|
|
} while (mPreviousGame && mImageFiles.at(index) == mPreviousGame);
|
|
|
|
path = mImageFiles.at(index)->getImagePath();
|
|
mGameName = mImageFiles.at(index)->getName();
|
|
mSystemName = mImageFiles.at(index)->getSystem()->getFullName();
|
|
mCurrentGame = mImageFiles.at(index);
|
|
|
|
// Don't display the same image again until we've cycled through all entries.
|
|
auto it = mImageFiles.begin() + index;
|
|
mImageFiles.erase(it);
|
|
}
|
|
|
|
void Screensaver::pickRandomVideo(std::string& path)
|
|
{
|
|
mCurrentGame = nullptr;
|
|
|
|
if (mVideoFiles.size() == 0)
|
|
return;
|
|
|
|
if (mVideoFiles.size() == 1) {
|
|
mPreviousGame = nullptr;
|
|
mCurrentGame = mVideoFiles.front();
|
|
path = mVideoFiles.front()->getVideoPath();
|
|
mGameName = mVideoFiles.front()->getName();
|
|
mSystemName = mVideoFiles.front()->getSystem()->getFullName();
|
|
mCurrentGame = mVideoFiles.front();
|
|
mVideoFiles.clear();
|
|
return;
|
|
}
|
|
|
|
unsigned int index;
|
|
do {
|
|
// Get a random number in range.
|
|
std::random_device randDev;
|
|
// Mersenne Twister pseudorandom number generator.
|
|
std::mt19937 engine {randDev()};
|
|
std::uniform_int_distribution<int> uniform_dist {0,
|
|
static_cast<int>(mVideoFiles.size()) - 1};
|
|
index = uniform_dist(engine);
|
|
} while (mPreviousGame && mVideoFiles.at(index) == mPreviousGame);
|
|
|
|
path = mVideoFiles.at(index)->getVideoPath();
|
|
mGameName = mVideoFiles.at(index)->getName();
|
|
mSystemName = mVideoFiles.at(index)->getSystem()->getFullName();
|
|
mCurrentGame = mVideoFiles.at(index);
|
|
|
|
// Don't play the same video again until we've cycled through all entries.
|
|
auto it = mVideoFiles.begin() + index;
|
|
mVideoFiles.erase(it);
|
|
}
|
|
|
|
void Screensaver::pickRandomCustomImage(std::string& path)
|
|
{
|
|
if (mImageCustomFiles.size() == 0)
|
|
return;
|
|
|
|
if (mImageCustomFiles.size() == 1) {
|
|
mPreviousCustomImage = mImageCustomFiles.front();
|
|
path = mImageCustomFiles.front();
|
|
mImageCustomFiles.clear();
|
|
return;
|
|
}
|
|
|
|
unsigned int index;
|
|
do {
|
|
// Get a random number in range.
|
|
std::random_device randDev;
|
|
// Mersenne Twister pseudorandom number generator.
|
|
std::mt19937 engine {randDev()};
|
|
std::uniform_int_distribution<int> uniform_dist {
|
|
0, static_cast<int>(mImageCustomFiles.size()) - 1};
|
|
index = uniform_dist(engine);
|
|
} while (mPreviousCustomImage != "" && mImageCustomFiles.at(index) == mPreviousCustomImage);
|
|
|
|
path = mImageCustomFiles.at(index);
|
|
mPreviousCustomImage = path;
|
|
mGameName = "";
|
|
mSystemName = "";
|
|
|
|
// Don't display the same image again until we've cycled through all entries.
|
|
auto it = mImageCustomFiles.begin() + index;
|
|
mImageCustomFiles.erase(it);
|
|
}
|
|
|
|
void Screensaver::generateOverlayInfo()
|
|
{
|
|
if (mGameName == "" || mSystemName == "")
|
|
return;
|
|
|
|
float posX {mRenderer->getScreenWidth() * 0.023f};
|
|
float posY {mRenderer->getScreenHeight() * 0.02f};
|
|
|
|
const bool favoritesOnly {
|
|
(mScreensaverType == "video" &&
|
|
Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")) ||
|
|
(mScreensaverType == "slideshow" &&
|
|
Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites"))};
|
|
|
|
std::string favoriteChar;
|
|
// Don't add the favorites character if only displaying favorite games.
|
|
if (!favoritesOnly && mCurrentGame && mCurrentGame->getFavorite())
|
|
favoriteChar.append(" ").append(ViewController::FAVORITE_CHAR);
|
|
|
|
const std::string gameName {Utils::String::toUpper(mGameName) + favoriteChar};
|
|
const std::string systemName {Utils::String::toUpper(mSystemName)};
|
|
const std::string overlayText {gameName + "\n" + systemName};
|
|
|
|
mGameOverlay = std::unique_ptr<TextCache>(
|
|
mGameOverlayFont.at(0)->buildTextCache(overlayText, posX, posY, 0xFFFFFFFF));
|
|
|
|
float textSizeX {0.0f};
|
|
float textSizeY {mGameOverlayFont[0].get()->sizeText(overlayText).y};
|
|
|
|
// There is a weird issue with sizeText() where the X size value is returned
|
|
// as too large if there are two rows in a string and the second row is longer
|
|
// than the first row. Possibly it's the newline character that is somehow
|
|
// injected in the size calculation. Regardless, this workaround is working
|
|
// fine for the time being.
|
|
if (mGameOverlayFont[0].get()->sizeText(gameName).x >
|
|
mGameOverlayFont[0].get()->sizeText(systemName).x)
|
|
textSizeX = mGameOverlayFont[0].get()->sizeText(gameName).x;
|
|
else
|
|
textSizeX = mGameOverlayFont[0].get()->sizeText(systemName).x;
|
|
|
|
float marginX {mRenderer->getScreenWidth() * 0.01f};
|
|
|
|
mGameOverlayRectangleCoords.clear();
|
|
mGameOverlayRectangleCoords.push_back(posX - marginX);
|
|
mGameOverlayRectangleCoords.push_back(posY);
|
|
mGameOverlayRectangleCoords.push_back(textSizeX + marginX * 2.0f);
|
|
mGameOverlayRectangleCoords.push_back(textSizeY);
|
|
}
|