ES-DE/es-app/src/Screensaver.cpp

647 lines
24 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// Screensaver.cpp
//
2020-11-10 21:18:20 +00:00
// Screensaver, supporting the following types:
// Dim, black, slideshow, video.
//
#include "Screensaver.h"
2017-11-01 22:21:10 +00:00
#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"
2020-11-10 21:18:20 +00:00
#include <random>
#include <time.h>
#include <unordered_map>
#if defined(_WIN64)
#include <cstring>
#endif
#define FADE_TIME 300.0f
Screensaver::Screensaver()
: mRenderer {Renderer::getInstance()}
, mWindow {Window::getInstance()}
2022-01-16 17:18:28 +00:00
, mState {STATE_INACTIVE}
, mImageScreensaver {nullptr}
, mVideoScreensaver {nullptr}
, mCurrentGame {nullptr}
, mPreviousGame {nullptr}
, mTimer {0}
, mMediaSwapTime {0}
, mTriggerNextGame {false}
, mHasMediaFiles {false}
, mFallbackScreensaver {false}
, mOpacity {0.0f}
, mDimValue {1.0}
, mRectangleFadeIn {50}
, mTextFadeIn {0}
, mSaturationAmount {1.0}
{
2020-11-10 21:18:20 +00:00
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;
2020-11-10 21:18:20 +00:00
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") {
2020-11-10 21:18:20 +00:00
if (generateMediaList) {
mImageFiles.clear();
mImageCustomFiles.clear();
}
// This creates a fade transition between the images.
mState = STATE_FADE_OUT_WINDOW;
mMediaSwapTime = Settings::getInstance()->getInt("ScreensaverSwapImageTimeout");
2020-11-10 21:18:20 +00:00
// Load a random image.
if (Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")) {
if (generateMediaList)
generateCustomImageList();
pickRandomCustomImage(path);
if (mImageCustomFiles.size() > 0)
mHasMediaFiles = true;
// Custom images are not tied to the game list.
mCurrentGame = nullptr;
}
else {
if (generateMediaList)
generateImageList();
pickRandomImage(path);
}
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.
2020-11-10 21:18:20 +00:00
if (mImageFiles.size() > 0 || mImageCustomFiles.size() > 0) {
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo"))
generateOverlayInfo();
2020-11-10 21:18:20 +00:00
if (!mImageScreensaver)
mImageScreensaver = std::make_unique<ImageComponent>(false, false);
2020-11-10 21:18:20 +00:00
mTimer = 0;
mImageScreensaver->setImage(path);
mImageScreensaver->setOrigin(0.5f, 0.5f);
mImageScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f,
Renderer::getScreenHeight() / 2.0f);
2020-11-10 21:18:20 +00:00
if (Settings::getInstance()->getBool("ScreensaverStretchImages"))
mImageScreensaver->setResize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
2020-11-10 21:18:20 +00:00
else
mImageScreensaver->setMaxSize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
2020-11-10 21:18:20 +00:00
}
mTimer = 0;
return;
}
else if (!mVideoScreensaver && (mScreensaverType == "video")) {
2020-11-10 21:18:20 +00:00
if (generateMediaList)
mVideoFiles.clear();
// This creates a fade transition between the videos.
mState = STATE_FADE_OUT_WINDOW;
mMediaSwapTime = Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout");
// Load a random video.
2020-11-10 21:18:20 +00:00
if (generateMediaList)
generateVideoList();
pickRandomVideo(path);
2020-11-10 21:18:20 +00:00
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);
2020-11-05 17:18:11 +00:00
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;
}
}
2020-11-10 21:18:20 +00:00
// No videos or images, just use a standard screensaver.
mState = STATE_SCREENSAVER_ACTIVE;
mCurrentGame = nullptr;
}
void Screensaver::stopScreensaver()
{
mImageScreensaver.reset();
mVideoScreensaver.reset();
mState = STATE_INACTIVE;
mDimValue = 1.0f;
mRectangleFadeIn = 50;
mTextFadeIn = 0;
mSaturationAmount = 1.0f;
if (mGameOverlay)
mGameOverlay.reset();
ViewController::getInstance()->startViewVideos();
2020-11-10 21:18:20 +00:00
}
void Screensaver::nextGame()
{
2020-11-10 21:18:20 +00:00
stopScreensaver();
startScreensaver(false);
}
void Screensaver::launchGame()
2020-11-10 21:18:20 +00:00
{
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;
2020-11-10 21:18:20 +00:00
// 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();
2020-11-10 21:18:20 +00:00
}
}
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);
// Only render the video if the state requires it.
if (static_cast<int>(mState) >= STATE_FADE_IN_VIDEO)
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);
// Only render the image if the state requires it.
2020-11-10 21:18:20 +00:00
if (static_cast<int>(mState) >= STATE_FADE_IN_VIDEO) {
if (mImageScreensaver->hasImage()) {
mImageScreensaver->setOpacity(1.0f - mOpacity);
mImageScreensaver->render(trans);
}
}
}
if (isScreensaverActive()) {
if (mScreensaverType == "slideshow") {
if (mHasMediaFiles) {
if (Settings::getInstance()->getBool("ScreensaverSlideshowScanlines"))
mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES);
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") &&
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)
{
2020-11-10 21:18:20 +00:00
// Use this to update the fade value for the current fade stage.
if (mState == STATE_FADE_OUT_WINDOW) {
mOpacity += static_cast<float>(deltaTime) / FADE_TIME;
if (mOpacity >= 1.0f) {
mOpacity = 1.0f;
2020-11-10 21:18:20 +00:00
// Update to the next state.
mState = STATE_FADE_IN_VIDEO;
}
}
2020-11-10 21:18:20 +00:00
else if (mState == STATE_FADE_IN_VIDEO) {
mOpacity -= static_cast<float>(deltaTime) / FADE_TIME;
if (mOpacity <= 0.0f) {
mOpacity = 0.0f;
// Update to the next state.
mState = STATE_SCREENSAVER_ACTIVE;
}
}
2020-11-10 21:18:20 +00:00
else if (mState == STATE_SCREENSAVER_ACTIVE) {
// 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;
2020-11-10 21:18:20 +00:00
nextGame();
}
}
2020-11-10 21:18:20 +00:00
// If we have a loaded a video or image, then update it.
2020-11-10 21:18:20 +00:00
if (mVideoScreensaver)
mVideoScreensaver->update(deltaTime);
if (mImageScreensaver)
mImageScreensaver->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;
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;
std::string imagePath {(*it2)->getImagePath()};
2020-11-10 21:18:20 +00:00
if (imagePath != "")
2021-09-19 17:46:59 +00:00
mImageFiles.push_back((*it2));
}
}
}
void Screensaver::generateVideoList()
{
const bool favoritesOnly {Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")};
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); ++it) {
2020-11-10 21:18:20 +00:00
// We only want nodes from game systems that are not collections.
if (!(*it)->isGameSystem() || (*it)->isCollection())
continue;
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;
std::string videoPath {(*it2)->getVideoPath()};
2020-11-10 21:18:20 +00:00
if (videoPath != "")
2021-09-19 17:46:59 +00:00
mVideoFiles.push_back((*it2));
}
}
}
void Screensaver::generateCustomImageList()
{
2020-11-10 21:18:20 +00:00
std::string imageDir = Utils::FileSystem::expandHomePath(
Settings::getInstance()->getString("ScreensaverSlideshowImageDir"));
// 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());
2020-11-10 21:18:20 +00:00
if (imageDir != "" && Utils::FileSystem::isDirectory(imageDir)) {
std::string imageFilter {".jpg, .JPG, .png, .PNG"};
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)) {
2020-11-10 21:18:20 +00:00
if (imageFilter.find(Utils::FileSystem::getExtension(*it)) != std::string::npos)
mImageCustomFiles.push_back(*it);
}
}
}
else {
LOG(LogWarning) << "Custom screensaver image directory '" << imageDir << "' does not exist";
}
}
void Screensaver::pickRandomImage(std::string& path)
{
2020-11-10 21:18:20 +00:00
mCurrentGame = nullptr;
2020-11-10 21:18:20 +00:00
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();
2020-11-10 21:18:20 +00:00
return;
}
2020-11-10 21:18:20 +00:00
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};
2020-11-10 21:18:20 +00:00
index = uniform_dist(engine);
} while (mPreviousGame && mImageFiles.at(index) == mPreviousGame);
2020-11-10 21:18:20 +00:00
path = mImageFiles.at(index)->getImagePath();
mGameName = mImageFiles.at(index)->getName();
mSystemName = mImageFiles.at(index)->getSystem()->getFullName();
mCurrentGame = mImageFiles.at(index);
}
void Screensaver::pickRandomVideo(std::string& path)
{
2020-11-10 21:18:20 +00:00
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();
2020-11-10 21:18:20 +00:00
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};
2020-11-10 21:18:20 +00:00
index = uniform_dist(engine);
} while (mPreviousGame && mVideoFiles.at(index) == mPreviousGame);
2020-11-10 21:18:20 +00:00
path = mVideoFiles.at(index)->getVideoPath();
mGameName = mVideoFiles.at(index)->getName();
mSystemName = mVideoFiles.at(index)->getSystem()->getFullName();
mCurrentGame = mVideoFiles.at(index);
}
void Screensaver::pickRandomCustomImage(std::string& path)
{
2020-11-10 21:18:20 +00:00
if (mImageCustomFiles.size() == 0)
return;
if (mImageCustomFiles.size() == 1) {
2020-11-10 21:18:20 +00:00
mPreviousCustomImage = mImageCustomFiles.front();
path = mImageCustomFiles.front();
return;
}
2020-11-10 21:18:20 +00:00
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};
2020-11-10 21:18:20 +00:00
index = uniform_dist(engine);
} while (mPreviousCustomImage != "" && mImageCustomFiles.at(index) == mPreviousCustomImage);
2020-11-10 21:18:20 +00:00
path = mImageCustomFiles.at(index);
mPreviousCustomImage = path;
mGameName = "";
mSystemName = "";
}
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);
}