Removed a lot of deprecated theme engine code from the legacy engine.

This commit is contained in:
Leon Styhre 2022-09-10 11:55:35 +02:00
parent 74d3e1f063
commit 596bc5e8af
15 changed files with 0 additions and 4438 deletions

View file

@ -1,356 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// BasicGamelistView.cpp
//
// Interface that defines a GamelistView of the type 'Basic'.
//
#include "views/gamelist/BasicGamelistView.h"
#include "CollectionSystemsManager.h"
#include "Settings.h"
#include "SystemData.h"
#include "UIModeController.h"
#include "utils/FileSystemUtil.h"
#include "views/ViewController.h"
BasicGamelistView::BasicGamelistView(Window* window, FileData* root)
: ISimpleGamelistView {window, root}
, mList {window}
{
mList.setSize(mSize.x, mSize.y * 0.8f);
mList.setPosition(0.0f, mSize.y * 0.2f);
mList.setDefaultZIndex(20.0f);
addChild(&mList);
populateList(root->getChildrenListToDisplay(), root);
}
void BasicGamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
ISimpleGamelistView::onThemeChanged(theme);
using namespace ThemeFlags;
mList.applyTheme(theme, getName(), "gamelist", ALL);
sortChildren();
}
void BasicGamelistView::onFileChanged(FileData* file, bool reloadGamelist)
{
if (reloadGamelist) {
// Might switch to a detailed view.
ViewController::getInstance()->reloadGamelistView(this);
return;
}
ISimpleGamelistView::onFileChanged(file, reloadGamelist);
}
void BasicGamelistView::populateList(const std::vector<FileData*>& files, FileData* firstEntry)
{
mFirstGameEntry = nullptr;
bool favoriteStar = true;
bool isEditing = false;
std::string editingCollection;
std::string inCollectionPrefix;
if (CollectionSystemsManager::getInstance()->isEditing()) {
editingCollection = CollectionSystemsManager::getInstance()->getEditingCollection();
isEditing = true;
}
// Read the settings that control whether a unicode star character should be added
// as a prefix to the game name.
if (files.size() > 0) {
if (files.front()->getSystem()->isCustomCollection())
favoriteStar = Settings::getInstance()->getBool("FavStarCustom");
else
favoriteStar = Settings::getInstance()->getBool("FavoritesStar");
}
mList.clear();
mHeaderText.setText(mRoot->getSystem()->getFullName());
if (files.size() > 0) {
for (auto it = files.cbegin(); it != files.cend(); ++it) {
if (!mFirstGameEntry && (*it)->getType() == GAME)
mFirstGameEntry = (*it);
// Add a leading tick mark icon to the game name if it's part of the custom collection
// currently being edited.
if (isEditing && (*it)->getType() == GAME) {
if (CollectionSystemsManager::getInstance()->inCustomCollection(editingCollection,
(*it))) {
if (Settings::getInstance()->getBool("SpecialCharsASCII"))
inCollectionPrefix = "! ";
else
inCollectionPrefix = ViewController::TICKMARK_CHAR + " ";
}
else {
inCollectionPrefix = "";
}
}
if ((*it)->getFavorite() && favoriteStar &&
mRoot->getSystem()->getName() != "favorites") {
if (Settings::getInstance()->getBool("SpecialCharsASCII"))
mList.add(inCollectionPrefix + "* " + (*it)->getName(), *it,
((*it)->getType() == FOLDER));
else
mList.add(inCollectionPrefix + ViewController::FAVORITE_CHAR + " " +
(*it)->getName(),
*it, ((*it)->getType() == FOLDER));
}
else if ((*it)->getType() == FOLDER && mRoot->getSystem()->getName() != "collections") {
if (Settings::getInstance()->getBool("SpecialCharsASCII"))
mList.add("# " + (*it)->getName(), *it, true);
else
mList.add(ViewController::FOLDER_CHAR + " " + (*it)->getName(), *it, true);
}
else {
mList.add(inCollectionPrefix + (*it)->getName(), *it, ((*it)->getType() == FOLDER));
}
}
}
else {
addPlaceholder(firstEntry);
}
generateGamelistInfo(getCursor(), firstEntry);
generateFirstLetterIndex(files);
}
void BasicGamelistView::setCursor(FileData* cursor)
{
if (!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) {
populateList(cursor->getParent()->getChildrenListToDisplay(), cursor->getParent());
mList.setCursor(cursor);
// Update our cursor stack in case our cursor just got set to some folder
// we weren't in before.
if (mCursorStack.empty() || mCursorStack.top() != cursor->getParent()) {
std::stack<FileData*> tmp;
FileData* ptr = cursor->getParent();
while (ptr && ptr != mRoot) {
tmp.push(ptr);
ptr = ptr->getParent();
}
// Flip the stack and put it in mCursorStack.
mCursorStack = std::stack<FileData*>();
while (!tmp.empty()) {
mCursorStack.push(tmp.top());
tmp.pop();
}
}
}
}
void BasicGamelistView::addPlaceholder(FileData* firstEntry)
{
// Empty list, add a placeholder.
FileData* placeholder;
if (firstEntry && firstEntry->getSystem()->isGroupedCustomCollection())
placeholder = firstEntry->getSystem()->getPlaceholder();
else
placeholder = this->mRoot->getSystem()->getPlaceholder();
mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER));
}
void BasicGamelistView::launch(FileData* game)
{
// This triggers ViewController to launch the game.
ViewController::getInstance()->triggerGameLaunch(game);
}
void BasicGamelistView::remove(FileData* game, bool deleteFile)
{
// Delete the game file on the filesystem.
if (deleteFile)
Utils::FileSystem::removeFile(game->getPath());
FileData* parent = game->getParent();
// Select next element in list, or previous if none.
if (getCursor() == game) {
std::vector<FileData*> siblings = parent->getChildrenListToDisplay();
auto gameIter = std::find(siblings.cbegin(), siblings.cend(), game);
unsigned int gamePos = static_cast<int>(std::distance(siblings.cbegin(), gameIter));
if (gameIter != siblings.cend()) {
if ((gamePos + 1) < siblings.size())
setCursor(siblings.at(gamePos + 1));
else if (gamePos > 1)
setCursor(siblings.at(gamePos - 1));
}
}
mList.remove(game);
if (mList.size() == 0)
addPlaceholder();
// If a game has been deleted, immediately remove the entry from gamelist.xml
// regardless of the value of the setting SaveGamelistsMode.
game->setDeletionFlag(true);
parent->getSystem()->writeMetaData();
// Remove before repopulating (removes from parent), then update the view.
delete game;
if (deleteFile) {
parent->sort(parent->getSortTypeFromString(parent->getSortTypeString()),
Settings::getInstance()->getBool("FavoritesFirst"));
onFileChanged(parent, false);
}
}
void BasicGamelistView::removeMedia(FileData* game)
{
std::string systemMediaDir = FileData::getMediaDirectory() + game->getSystem()->getName();
std::string mediaType;
std::string path;
// Stop the video player, especially important on Windows as the file would otherwise be locked.
onStopVideo();
// If there are no media files left in the directory after the deletion, then remove
// the directory too. Remove any empty parent directories as well.
auto removeEmptyDirFunc = [](std::string systemMediaDir, std::string mediaType,
std::string path) {
std::string parentPath = Utils::FileSystem::getParent(path);
while (parentPath != systemMediaDir + "/" + mediaType) {
if (Utils::FileSystem::getDirContent(parentPath).size() == 0) {
Utils::FileSystem::removeDirectory(parentPath);
parentPath = Utils::FileSystem::getParent(parentPath);
}
else {
break;
}
}
};
// Remove all game media files on the filesystem.
while (Utils::FileSystem::exists(game->getVideoPath())) {
mediaType = "videos";
path = game->getVideoPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getMiximagePath())) {
mediaType = "miximages";
path = game->getMiximagePath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getScreenshotPath())) {
mediaType = "screenshots";
path = game->getScreenshotPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getTitleScreenPath())) {
mediaType = "titlescreens";
path = game->getTitleScreenPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getCoverPath())) {
mediaType = "covers";
path = game->getCoverPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getBackCoverPath())) {
mediaType = "backcovers";
path = game->getBackCoverPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getFanArtPath())) {
mediaType = "fanart";
path = game->getFanArtPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getMarqueePath())) {
mediaType = "marquees";
path = game->getMarqueePath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->get3DBoxPath())) {
mediaType = "3dboxes";
path = game->get3DBoxPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getPhysicalMediaPath())) {
mediaType = "physicalmedia";
path = game->getPhysicalMediaPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getThumbnailPath())) {
mediaType = "thumbnails";
path = game->getThumbnailPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
}
std::vector<HelpPrompt> BasicGamelistView::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1)
prompts.push_back(HelpPrompt("left/right", "system"));
if (mRoot->getSystem()->getThemeFolder() == "custom-collections" && mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing == ViewController::GAMELIST)
prompts.push_back(HelpPrompt("a", "enter"));
else
prompts.push_back(HelpPrompt("a", "launch"));
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()->getBool("RandomAddButton"))
prompts.push_back(HelpPrompt("thumbstickclick", "random"));
if (mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
!CollectionSystemsManager::getInstance()->isEditing() && mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing == ViewController::GAMELIST &&
ViewController::getInstance()->getState().viewstyle != ViewController::BASIC) {
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();
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();
prompts.push_back(HelpPrompt("y", prompt));
}
return prompts;
}

View file

@ -1,60 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// BasicGamelistView.h
//
// Interface that defines a GamelistView of the type 'basic'.
//
#ifndef ES_APP_VIEWS_GAMELIST_BASIC_GAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_BASIC_GAMELIST_VIEW_H
#include "components/TextListComponent.h"
#include "views/gamelist/ISimpleGamelistView.h"
class BasicGamelistView : public ISimpleGamelistView
{
public:
BasicGamelistView(Window* window, FileData* root);
// Called when a FileData* is added, has its metadata changed, or is removed.
void onFileChanged(FileData* file, bool reloadGamelist) override;
void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
void setCursor(FileData* cursor) override;
FileData* getCursor() override { return mList.getSelected(); }
FileData* getNextEntry() override { return mList.getNext(); }
FileData* getPreviousEntry() override { return mList.getPrevious(); }
FileData* getFirstEntry() override { return mList.getFirst(); }
FileData* getLastEntry() override { return mList.getLast(); }
FileData* getFirstGameEntry() override { return mFirstGameEntry; }
std::string getName() const override { return "basic"; }
std::vector<HelpPrompt> getHelpPrompts() override;
bool isListScrolling() override { return mList.isScrolling(); }
void stopListScrolling() override { mList.stopScrolling(); }
const std::vector<std::string>& getFirstLetterIndex() override { return mFirstLetterIndex; }
void addPlaceholder(FileData* firstEntry = nullptr) override;
void launch(FileData* game) override;
protected:
std::string getQuickSystemSelectRightButton() override { return "right"; }
std::string getQuickSystemSelectLeftButton() override { return "left"; }
void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override;
void remove(FileData* game, bool deleteFile) override;
void removeMedia(FileData* game) override;
TextListComponent<FileData*> mList;
// Points to the first game in the list, i.e. the first entry which is of the type 'GAME'.
FileData* mFirstGameEntry;
std::string FAVORITE_CHAR;
std::string FOLDER_CHAR;
};
#endif // ES_APP_VIEWS_GAMELIST_BASIC_GAMELIST_VIEW_H

View file

@ -1,528 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// DetailedGamelistView.cpp
//
// Interface that defines a GamelistView of the type 'detailed'.
//
#include "views/gamelist/DetailedGamelistView.h"
#include "CollectionSystemsManager.h"
#include "SystemData.h"
#include "animations/LambdaAnimation.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650
DetailedGamelistView::DetailedGamelistView(Window* window, FileData* root)
: BasicGamelistView(window, root)
, mThumbnail(window)
, mMarquee(window)
, mImage(window)
, mLblRating(window)
, mLblReleaseDate(window)
, mLblDeveloper(window)
, mLblPublisher(window)
, mLblGenre(window)
, mLblPlayers(window)
, mLblLastPlayed(window)
, mLblPlayCount(window)
, mRating(window)
, mReleaseDate(window)
, mDeveloper(window)
, mPublisher(window)
, mGenre(window)
, mPlayers(window)
, mLastPlayed(window)
, mPlayCount(window)
, mName(window)
, mBadges(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mLastUpdated(nullptr)
{
const float padding = 0.01f;
mList.setPosition(mSize.x * (0.50f + padding), mList.getPosition().y);
mList.setSize(mSize.x * (0.50f - padding), mList.getSize().y);
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
// Thumbnail.
mThumbnail.setOrigin(0.5f, 0.5f);
mThumbnail.setPosition(2.0f, 2.0f);
mThumbnail.setVisible(false);
mThumbnail.setMaxSize(mSize.x * (0.25f - 2.0f * padding), mSize.y * 0.10f);
mThumbnail.setDefaultZIndex(25.0f);
addChild(&mThumbnail);
// Marquee.
mMarquee.setOrigin(0.5f, 0.5f);
// Default to off the screen.
mMarquee.setPosition(2.0f, 2.0f);
mMarquee.setVisible(false);
mMarquee.setMaxSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.18f);
mMarquee.setDefaultZIndex(35.0f);
addChild(&mMarquee);
// Image.
mImage.setOrigin(0.5f, 0.5f);
mImage.setPosition(mSize.x * 0.25f, mList.getPosition().y + mSize.y * 0.2125f);
mImage.setMaxSize(mSize.x * (0.50f - 2.0f * padding), mSize.y * 0.4f);
mImage.setDefaultZIndex(30.0f);
addChild(&mImage);
// Metadata labels + values.
mLblRating.setText("Rating: ", false);
addChild(&mLblRating);
addChild(&mRating);
mLblReleaseDate.setText("Released: ", false);
addChild(&mLblReleaseDate);
addChild(&mReleaseDate);
mLblDeveloper.setText("Developer: ", false);
addChild(&mLblDeveloper);
addChild(&mDeveloper);
mLblPublisher.setText("Publisher: ", false);
addChild(&mLblPublisher);
addChild(&mPublisher);
mLblGenre.setText("Genre: ", false);
addChild(&mLblGenre);
addChild(&mGenre);
mLblPlayers.setText("Players: ", false);
addChild(&mLblPlayers);
addChild(&mPlayers);
mLblLastPlayed.setText("Last played: ", false);
addChild(&mLblLastPlayed);
mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: ", false);
addChild(&mLblPlayCount);
addChild(&mPlayCount);
// Badges.
addChild(&mBadges);
mBadges.setOrigin(0.5f, 0.5f);
mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f);
mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f);
mBadges.setDefaultZIndex(50.0f);
mName.setPosition(mSize.x, mSize.y);
mName.setDefaultZIndex(40.0f);
mName.setColor(0xAAAAAAFF);
mName.setFont(Font::get(FONT_SIZE_MEDIUM));
mName.setHorizontalAlignment(ALIGN_CENTER);
addChild(&mName);
mDescContainer.setPosition(mSize.x * padding, mSize.y * 0.65f);
mDescContainer.setSize(mSize.x * (0.50f - 2.0f * padding),
mSize.y - mDescContainer.getPosition().y);
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40.0f);
addChild(&mDescContainer);
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescContainer.addChild(&mDescription);
mGamelistInfo.setOrigin(0.5f, 0.5f);
mGamelistInfo.setFont(Font::get(FONT_SIZE_SMALL));
mGamelistInfo.setDefaultZIndex(50.0f);
mGamelistInfo.setVisible(true);
addChild(&mGamelistInfo);
initMDLabels();
initMDValues();
}
void DetailedGamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
BasicGamelistView::onThemeChanged(theme);
using namespace ThemeFlags;
mThumbnail.applyTheme(theme, getName(), "md_thumbnail",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mName.applyTheme(theme, getName(), "md_name", ALL);
mBadges.applyTheme(theme, getName(), "md_badges", ALL);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8);
std::vector<std::string> lblElements = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"};
for (unsigned int i = 0; i < labels.size(); ++i)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8);
std::vector<std::string> valElements = {"md_rating", "md_releasedate", "md_developer",
"md_publisher", "md_genre", "md_players",
"md_lastplayed", "md_playcount"};
for (unsigned int i = 0; i < values.size(); ++i)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT);
// If there is no position defined in the theme for gamelistInfo, then hide it.
if (mGamelistInfo.getPosition() == glm::vec3 {})
mGamelistInfo.setVisible(false);
else
mGamelistInfo.setVisible(true);
sortChildren();
}
void DetailedGamelistView::initMDLabels()
{
std::vector<TextComponent*> components = getMDLabels();
const unsigned int colCount = 2;
const unsigned int rowCount = static_cast<int>(components.size() / 2);
glm::vec3 start {mSize.x * 0.01f, mSize.y * 0.625f, 0.0f};
const float colSize = (mSize.x * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y;
for (unsigned int i = 0; i < components.size(); ++i) {
const unsigned int row = i % rowCount;
glm::vec3 pos {};
if (row == 0) {
pos = start + glm::vec3 {colSize * (i / rowCount), 0.0f, 0.0f};
}
else {
// Work from the last component.
GuiComponent* lc = components[i - 1];
pos = lc->getPosition() + glm::vec3 {0.0f, lc->getSize().y + rowPadding, 0.0f};
}
components[i]->setFont(Font::get(FONT_SIZE_SMALL));
components[i]->setPosition(pos);
components[i]->setDefaultZIndex(40.0f);
}
}
void DetailedGamelistView::initMDValues()
{
std::vector<TextComponent*> labels = getMDLabels();
std::vector<GuiComponent*> values = getMDValues();
std::shared_ptr<Font> defaultFont = Font::get(FONT_SIZE_SMALL);
mRating.setSize(defaultFont->getHeight() * 5.0f, static_cast<float>(defaultFont->getHeight()));
mReleaseDate.setFont(defaultFont);
mDeveloper.setFont(defaultFont);
mPublisher.setFont(defaultFont);
mGenre.setFont(defaultFont);
mPlayers.setFont(defaultFont);
mLastPlayed.setFont(defaultFont);
mPlayCount.setFont(defaultFont);
float bottom = 0.0f;
const float colSize = (mSize.x * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); ++i) {
const float heightDiff = (labels[i]->getSize().y - values[i]->getSize().y) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() +
glm::vec3 {labels[i]->getSize().x, heightDiff, 0.0f});
values[i]->setSize(colSize - labels[i]->getSize().x, values[i]->getSize().y);
values[i]->setDefaultZIndex(40.0f);
float testBot = values[i]->getPosition().y + values[i]->getSize().y;
if (testBot > bottom)
bottom = testBot;
}
mDescContainer.setPosition(mDescContainer.getPosition().x, bottom + mSize.y * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x, mSize.y - mDescContainer.getPosition().y);
}
void DetailedGamelistView::updateInfoPanel()
{
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected();
// If the game data has already been rendered to the info panel, then skip it this time.
if (file == mLastUpdated)
return;
if (!mList.isScrolling())
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 (mList.isScrolling())
if ((mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true") ||
(mLastUpdated->getSystem()->isCustomCollection() &&
mLastUpdated->getPath() == mLastUpdated->getSystem()->getName()))
hideMetaDataFields = true;
if (hideMetaDataFields) {
mLblRating.setVisible(false);
mRating.setVisible(false);
mLblReleaseDate.setVisible(false);
mReleaseDate.setVisible(false);
mLblDeveloper.setVisible(false);
mDeveloper.setVisible(false);
mLblPublisher.setVisible(false);
mPublisher.setVisible(false);
mLblGenre.setVisible(false);
mGenre.setVisible(false);
mLblPlayers.setVisible(false);
mPlayers.setVisible(false);
mLblLastPlayed.setVisible(false);
mLastPlayed.setVisible(false);
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
mBadges.setVisible(false);
}
else {
mLblRating.setVisible(true);
mRating.setVisible(true);
mLblReleaseDate.setVisible(true);
mReleaseDate.setVisible(true);
mLblDeveloper.setVisible(true);
mDeveloper.setVisible(true);
mLblPublisher.setVisible(true);
mPublisher.setVisible(true);
mLblGenre.setVisible(true);
mGenre.setVisible(true);
mLblPlayers.setVisible(true);
mPlayers.setVisible(true);
mLblLastPlayed.setVisible(true);
mLastPlayed.setVisible(true);
mLblPlayCount.setVisible(true);
mPlayCount.setVisible(true);
mBadges.setVisible(true);
}
bool fadingOut = false;
if (file == nullptr) {
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) {
mThumbnail.setImage(mRandomGame->getThumbnailPath());
mMarquee.setImage(mRandomGame->getMarqueePath(), false, true);
mImage.setImage(mRandomGame->getImagePath());
}
else {
mThumbnail.setImage("");
mMarquee.setImage("");
mImage.setImage("");
}
}
else {
mThumbnail.setImage(file->getThumbnailPath());
mMarquee.setImage(file->getMarqueePath(), false, true);
mImage.setImage(file->getImagePath());
}
// 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.
std::string gamelistInfoString;
Alignment infoAlign = mGamelistInfo.getHorizontalAlignment();
if (mIsFolder && infoAlign == ALIGN_RIGHT)
gamelistInfoString = ViewController::FOLDER_CHAR + " ";
if (mIsFiltered) {
if (mFilteredGameCountAll == mFilteredGameCount)
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " / " +
std::to_string(mGameCount);
else
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
" / " + std::to_string(mGameCount);
}
else {
gamelistInfoString +=
ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
std::to_string(mFavoritesGameCount);
}
if (mIsFolder && infoAlign != ALIGN_RIGHT)
gamelistInfoString += " " + ViewController::FOLDER_CHAR;
mGamelistInfo.setValue(gamelistInfoString);
// Fade in the game image.
auto func = [this](float t) {
mImage.setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
};
mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
mDescription.setText(file->metadata.get("desc"));
mDescContainer.reset();
mRating.setValue(file->metadata.get("rating"));
mReleaseDate.setValue(file->metadata.get("releasedate"));
mDeveloper.setValue(file->metadata.get("developer"));
mPublisher.setValue(file->metadata.get("publisher"));
mGenre.setValue(file->metadata.get("genre"));
mPlayers.setValue(file->metadata.get("players"));
// Populate the badge slots based on game metadata.
std::vector<BadgeComponent::BadgeInfo> badgeSlots;
for (auto badge : mBadges.getBadgeTypes()) {
BadgeComponent::BadgeInfo badgeInfo;
badgeInfo.badgeType = badge;
if (badge == "controller") {
if (file->metadata.get("controller").compare("") != 0) {
badgeInfo.gameController = file->metadata.get("controller");
badgeSlots.push_back(badgeInfo);
}
}
else if (badge == "altemulator") {
if (file->metadata.get(badge).compare("") != 0)
badgeSlots.push_back(badgeInfo);
}
else {
if (file->metadata.get(badge).compare("true") == 0)
badgeSlots.push_back(badgeInfo);
}
}
mBadges.setBadges(badgeSlots);
mName.setValue(file->metadata.get("name"));
if (file->getType() == GAME) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
}
else if (file->getType() == FOLDER) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
}
}
fadingOut = false;
}
std::vector<GuiComponent*> comps = getMDValues();
comps.push_back(&mThumbnail);
comps.push_back(&mMarquee);
comps.push_back(&mImage);
comps.push_back(&mDescription);
comps.push_back(&mName);
comps.push_back(&mBadges);
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
}
void DetailedGamelistView::launch(FileData* game)
{
ViewController::getInstance()->triggerGameLaunch(game);
}
std::vector<TextComponent*> DetailedGamelistView::getMDLabels()
{
std::vector<TextComponent*> ret;
ret.push_back(&mLblRating);
ret.push_back(&mLblReleaseDate);
ret.push_back(&mLblDeveloper);
ret.push_back(&mLblPublisher);
ret.push_back(&mLblGenre);
ret.push_back(&mLblPlayers);
ret.push_back(&mLblLastPlayed);
ret.push_back(&mLblPlayCount);
return ret;
}
std::vector<GuiComponent*> DetailedGamelistView::getMDValues()
{
std::vector<GuiComponent*> ret;
ret.push_back(&mRating);
ret.push_back(&mReleaseDate);
ret.push_back(&mDeveloper);
ret.push_back(&mPublisher);
ret.push_back(&mGenre);
ret.push_back(&mPlayers);
ret.push_back(&mLastPlayed);
ret.push_back(&mPlayCount);
return ret;
}
void DetailedGamelistView::update(int deltaTime)
{
BasicGamelistView::update(deltaTime);
mImage.update(deltaTime);
if (ViewController::getInstance()->getGameLaunchTriggered() && mImage.isAnimationPlaying(0))
mImage.finishAnimation(0);
}
void DetailedGamelistView::onShow()
{
// Reset any Lottie animations.
for (auto extra : mThemeExtras)
extra->resetFileAnimation();
mLastUpdated = nullptr;
GuiComponent::onShow();
updateInfoPanel();
}

View file

@ -1,73 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// DetailedGamelistView.h
//
// Interface that defines a GamelistView of the type 'detailed'.
//
#ifndef ES_APP_VIEWS_GAMELIST_DETAILED_GAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_DETAILED_GAMELIST_VIEW_H
#include "components/BadgeComponent.h"
#include "components/DateTimeComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "views/gamelist/BasicGamelistView.h"
class DetailedGamelistView : public BasicGamelistView
{
public:
DetailedGamelistView(Window* window, FileData* root);
void onShow() override;
void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
std::string getName() const override { return "detailed"; }
void launch(FileData* game) override;
void preloadGamelist() override { updateInfoPanel(); }
protected:
void update(int deltaTime) override;
private:
void updateInfoPanel();
void initMDLabels();
void initMDValues();
ImageComponent mThumbnail;
ImageComponent mMarquee;
ImageComponent mImage;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
RatingComponent mRating;
DateTimeComponent mReleaseDate;
TextComponent mDeveloper;
TextComponent mPublisher;
TextComponent mGenre;
TextComponent mPlayers;
DateTimeComponent mLastPlayed;
TextComponent mPlayCount;
TextComponent mName;
BadgeComponent mBadges;
std::vector<TextComponent*> getMDLabels();
std::vector<GuiComponent*> getMDValues();
ScrollableContainer mDescContainer;
TextComponent mDescription;
TextComponent mGamelistInfo;
FileData* mLastUpdated;
};
#endif // ES_APP_VIEWS_GAMELIST_DETAILED_GAMELIST_VIEW_H

View file

@ -1,743 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GridGamelistView.cpp
//
// Interface that defines a GamelistView of the type 'grid'.
//
#include "views/gamelist/GridGamelistView.h"
#include "CollectionSystemsManager.h"
#include "Settings.h"
#include "Sound.h"
#include "SystemData.h"
#include "UIModeController.h"
#include "animations/LambdaAnimation.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650
GridGamelistView::GridGamelistView(Window* window, FileData* root)
: ISimpleGamelistView(window, root)
, mGrid(window)
, mMarquee(window)
, mImage(window)
, mLblRating(window)
, mLblReleaseDate(window)
, mLblDeveloper(window)
, mLblPublisher(window)
, mLblGenre(window)
, mLblPlayers(window)
, mLblLastPlayed(window)
, mLblPlayCount(window)
, mBadges(window)
, mRating(window)
, mReleaseDate(window)
, mDeveloper(window)
, mPublisher(window)
, mGenre(window)
, mPlayers(window)
, mLastPlayed(window)
, mPlayCount(window)
, mName(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
{
const float padding = 0.01f;
mGrid.setPosition(mSize.x * 0.1f, mSize.y * 0.1f);
mGrid.setDefaultZIndex(20.0f);
mGrid.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
addChild(&mGrid);
populateList(root->getChildrenListToDisplay(), root);
// Metadata labels + values.
addChild(&mBadges);
mLblRating.setText("Rating: ", false);
addChild(&mLblRating);
addChild(&mRating);
mLblReleaseDate.setText("Released: ", false);
addChild(&mLblReleaseDate);
addChild(&mReleaseDate);
mLblDeveloper.setText("Developer: ", false);
addChild(&mLblDeveloper);
addChild(&mDeveloper);
mLblPublisher.setText("Publisher: ", false);
addChild(&mLblPublisher);
addChild(&mPublisher);
mLblGenre.setText("Genre: ", false);
addChild(&mLblGenre);
addChild(&mGenre);
mLblPlayers.setText("Players: ", false);
addChild(&mLblPlayers);
addChild(&mPlayers);
mLblLastPlayed.setText("Last played: ", false);
addChild(&mLblLastPlayed);
mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: ", false);
addChild(&mLblPlayCount);
addChild(&mPlayCount);
mName.setPosition(mSize.x, mSize.y);
mName.setDefaultZIndex(40.0f);
mName.setColor(0xAAAAAAFF);
mName.setFont(Font::get(FONT_SIZE_MEDIUM));
mName.setHorizontalAlignment(ALIGN_CENTER);
addChild(&mName);
mDescContainer.setPosition(mSize.x * padding, mSize.y * 0.65f);
mDescContainer.setSize(mSize.x * (0.50f - 2.0f * padding),
mSize.y - mDescContainer.getPosition().y);
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40.0f);
addChild(&mDescContainer);
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescContainer.addChild(&mDescription);
mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x * 0.25f, mSize.y * 0.10f);
mMarquee.setMaxSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.18f);
mMarquee.setDefaultZIndex(35.0f);
mMarquee.setVisible(false);
addChild(&mMarquee);
mImage.setOrigin(0.5f, 0.5f);
mImage.setPosition(2.0f, 2.0f);
mImage.setMaxSize(mSize.x * (0.50f - 2.0f * padding), mSize.y * 0.4f);
mImage.setDefaultZIndex(10.0f);
mImage.setVisible(false);
addChild(&mImage);
mGamelistInfo.setOrigin(0.5f, 0.5f);
mGamelistInfo.setFont(Font::get(FONT_SIZE_SMALL));
mGamelistInfo.setDefaultZIndex(50.0f);
mGamelistInfo.setVisible(true);
addChild(&mGamelistInfo);
initMDLabels();
initMDValues();
updateInfoPanel();
}
void GridGamelistView::onFileChanged(FileData* file, bool reloadGamelist)
{
if (reloadGamelist) {
// Might switch to a detailed view.
ViewController::getInstance()->reloadGamelistView(this);
return;
}
ISimpleGamelistView::onFileChanged(file, reloadGamelist);
}
void GridGamelistView::setCursor(FileData* cursor)
{
if (!mGrid.setCursor(cursor) && (!cursor->isPlaceHolder())) {
populateList(cursor->getParent()->getChildrenListToDisplay(), cursor->getParent());
mGrid.setCursor(cursor);
// Update our cursor stack in case our cursor just got set to some folder
// we weren't in before.
if (mCursorStack.empty() || mCursorStack.top() != cursor->getParent()) {
std::stack<FileData*> tmp;
FileData* ptr = cursor->getParent();
while (ptr && ptr != mRoot) {
tmp.push(ptr);
ptr = ptr->getParent();
}
// Flip the stack and put it in mCursorStack.
mCursorStack = std::stack<FileData*>();
while (!tmp.empty()) {
mCursorStack.push(tmp.top());
tmp.pop();
}
}
}
}
bool GridGamelistView::input(InputConfig* config, Input input)
{
if (input.value == 0 &&
(config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
(config->isMappedLike("up", input)) || (config->isMappedLike("down", input))))
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
if (input.value != 0 && config->isMappedLike("righttrigger", input)) {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mGrid.setCursor(mGrid.getLast());
}
if (input.value != 0 && config->isMappedLike("lefttrigger", input)) {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mGrid.setCursor(mGrid.getFirst());
}
if (config->isMappedLike("left", input) || config->isMappedLike("right", input))
return GuiComponent::input(config, input);
return ISimpleGamelistView::input(config, input);
}
const std::string GridGamelistView::getImagePath(FileData* file)
{
ImageSource src = mGrid.getImageSource();
if (src == ImageSource::IMAGE)
return file->getImagePath();
else if (src == ImageSource::MIXIMAGE)
return file->getMiximagePath();
else if (src == ImageSource::SCREENSHOT)
return file->getScreenshotPath();
else if (src == ImageSource::COVER)
return file->getCoverPath();
else if (src == ImageSource::MARQUEE)
return file->getMarqueePath();
else if (src == ImageSource::BOX3D)
return file->get3DBoxPath();
// If no thumbnail was found, then use the image media type.
if (file->getThumbnailPath() == "")
return file->getImagePath();
return file->getThumbnailPath();
}
void GridGamelistView::populateList(const std::vector<FileData*>& files, FileData* firstEntry)
{
firstGameEntry = nullptr;
mGrid.clear();
mHeaderText.setText(mRoot->getSystem()->getFullName());
if (files.size() > 0) {
for (auto it = files.cbegin(); it != files.cend(); ++it) {
if (!firstGameEntry && (*it)->getType() == GAME)
firstGameEntry = (*it);
mGrid.add((*it)->getName(), getImagePath(*it), *it);
}
}
else {
addPlaceholder(firstEntry);
}
generateGamelistInfo(getCursor(), firstEntry);
generateFirstLetterIndex(files);
}
void GridGamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
ISimpleGamelistView::onThemeChanged(theme);
using namespace ThemeFlags;
mGrid.applyTheme(theme, getName(), "gamegrid", ALL);
mName.applyTheme(theme, getName(), "md_name", ALL);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8);
std::vector<std::string> lblElements = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"};
for (unsigned int i = 0; i < labels.size(); ++i)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8);
std::vector<std::string> valElements = {"md_rating", "md_releasedate", "md_developer",
"md_publisher", "md_genre", "md_players",
"md_lastplayed", "md_playcount"};
for (unsigned int i = 0; i < values.size(); ++i)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
// Repopulate list in case a new theme is displaying a different image.
// Preserve selection.
FileData* file = mGrid.getSelected();
populateList(mRoot->getChildrenListToDisplay(), mRoot);
mGrid.setCursor(file);
mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT);
// If there is no position defined in the theme for gamelistInfo, then hide it.
if (mGamelistInfo.getPosition() == glm::vec3 {})
mGamelistInfo.setVisible(false);
else
mGamelistInfo.setVisible(true);
sortChildren();
}
void GridGamelistView::onShow()
{
// Reset any Lottie animations.
for (auto extra : mThemeExtras)
extra->resetFileAnimation();
GuiComponent::onShow();
updateInfoPanel();
}
void GridGamelistView::initMDLabels()
{
std::vector<TextComponent*> components = getMDLabels();
const unsigned int colCount = 2;
const unsigned int rowCount = static_cast<int>(components.size() / 2);
glm::vec3 start {mSize.x * 0.01f, mSize.y * 0.625f, 0.0f};
const float colSize = (mSize.x * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y;
for (unsigned int i = 0; i < components.size(); ++i) {
const unsigned int row = i % rowCount;
glm::vec3 pos {};
if (row == 0) {
pos = start + glm::vec3 {colSize * (i / rowCount), 0.0f, 0.0f};
}
else {
// Work from the last component.
GuiComponent* lc = components[i - 1];
pos = lc->getPosition() + glm::vec3 {0.0f, lc->getSize().y + rowPadding, 0.0f};
}
components[i]->setFont(Font::get(FONT_SIZE_SMALL));
components[i]->setPosition(pos);
components[i]->setDefaultZIndex(40.0f);
}
}
void GridGamelistView::initMDValues()
{
std::vector<TextComponent*> labels = getMDLabels();
std::vector<GuiComponent*> values = getMDValues();
std::shared_ptr<Font> defaultFont = Font::get(FONT_SIZE_SMALL);
mRating.setSize(defaultFont->getHeight() * 5.0f, static_cast<float>(defaultFont->getHeight()));
mReleaseDate.setFont(defaultFont);
mDeveloper.setFont(defaultFont);
mPublisher.setFont(defaultFont);
mGenre.setFont(defaultFont);
mPlayers.setFont(defaultFont);
mLastPlayed.setFont(defaultFont);
mPlayCount.setFont(defaultFont);
float bottom = 0.0f;
const float colSize = (mSize.x * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); ++i) {
const float heightDiff = (labels[i]->getSize().y - values[i]->getSize().y) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() +
glm::vec3 {labels[i]->getSize().x, heightDiff, 0.0f});
values[i]->setSize(colSize - labels[i]->getSize().x, values[i]->getSize().y);
values[i]->setDefaultZIndex(40.0f);
float testBot = values[i]->getPosition().y + values[i]->getSize().y;
if (testBot > bottom)
bottom = testBot;
}
mDescContainer.setPosition(mDescContainer.getPosition().x, bottom + mSize.y * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x, mSize.y - mDescContainer.getPosition().y);
}
void GridGamelistView::updateInfoPanel()
{
FileData* file = (mGrid.size() == 0 || mGrid.isScrolling()) ? nullptr : mGrid.getSelected();
bool hideMetaDataFields = false;
if (file)
hideMetaDataFields = (file->metadata.get("hidemetadata") == "true");
if (hideMetaDataFields) {
mLblRating.setVisible(false);
mRating.setVisible(false);
mLblReleaseDate.setVisible(false);
mReleaseDate.setVisible(false);
mLblDeveloper.setVisible(false);
mDeveloper.setVisible(false);
mLblPublisher.setVisible(false);
mPublisher.setVisible(false);
mLblGenre.setVisible(false);
mGenre.setVisible(false);
mLblPlayers.setVisible(false);
mPlayers.setVisible(false);
mLblLastPlayed.setVisible(false);
mLastPlayed.setVisible(false);
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
}
else {
mLblRating.setVisible(true);
mRating.setVisible(true);
mLblReleaseDate.setVisible(true);
mReleaseDate.setVisible(true);
mLblDeveloper.setVisible(true);
mDeveloper.setVisible(true);
mLblPublisher.setVisible(true);
mPublisher.setVisible(true);
mLblGenre.setVisible(true);
mGenre.setVisible(true);
mLblPlayers.setVisible(true);
mPlayers.setVisible(true);
mLblLastPlayed.setVisible(true);
mLastPlayed.setVisible(true);
mLblPlayCount.setVisible(true);
mPlayCount.setVisible(true);
}
bool fadingOut = false;
if (file == nullptr) {
fadingOut = true;
}
else {
mMarquee.setImage(file->getMarqueePath(), false, 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.
std::string gamelistInfoString;
Alignment infoAlign = mGamelistInfo.getHorizontalAlignment();
if (mIsFolder && infoAlign == ALIGN_RIGHT)
gamelistInfoString = ViewController::FOLDER_CHAR + " ";
if (mIsFiltered) {
if (mFilteredGameCountAll == mFilteredGameCount)
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " / " +
std::to_string(mGameCount);
else
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
" / " + std::to_string(mGameCount);
}
else {
gamelistInfoString +=
ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
std::to_string(mFavoritesGameCount);
}
if (mIsFolder && infoAlign != ALIGN_RIGHT)
gamelistInfoString += " " + ViewController::FOLDER_CHAR;
mGamelistInfo.setValue(gamelistInfoString);
// Fade in the game image.
auto func = [this](float t) {
mImage.setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
};
mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
mDescription.setText(file->metadata.get("desc"));
mDescContainer.reset();
mRating.setValue(file->metadata.get("rating"));
mReleaseDate.setValue(file->metadata.get("releasedate"));
mDeveloper.setValue(file->metadata.get("developer"));
mPublisher.setValue(file->metadata.get("publisher"));
mGenre.setValue(file->metadata.get("genre"));
mPlayers.setValue(file->metadata.get("players"));
mName.setValue(file->metadata.get("name"));
if (file->getType() == GAME) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
}
else if (file->getType() == FOLDER) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
}
}
fadingOut = false;
}
std::vector<GuiComponent*> comps = getMDValues();
comps.push_back(&mDescription);
comps.push_back(&mName);
comps.push_back(&mMarquee);
comps.push_back(&mImage);
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
// TODO: This does not seem to work, needs to be reviewed later.
// auto func = [comp](float t) {
auto func = [](float t) {
// comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
}
void GridGamelistView::addPlaceholder(FileData* firstEntry)
{
// Empty list, add a placeholder.
FileData* placeholder;
if (firstEntry && firstEntry->getSystem()->isGroupedCustomCollection())
placeholder = firstEntry->getSystem()->getPlaceholder();
else
placeholder = this->mRoot->getSystem()->getPlaceholder();
mGrid.add(placeholder->getName(), "", placeholder);
}
void GridGamelistView::launch(FileData* game)
{
// This triggers ViewController to launch the game.
ViewController::getInstance()->triggerGameLaunch(game);
}
void GridGamelistView::remove(FileData* game, bool deleteFile)
{
// Delete the game file on the filesystem.
if (deleteFile)
Utils::FileSystem::removeFile(game->getPath());
FileData* parent = game->getParent();
// Select next element in list, or previous if none.
if (getCursor() == game) {
std::vector<FileData*> siblings = parent->getChildrenListToDisplay();
auto gameIter = std::find(siblings.cbegin(), siblings.cend(), game);
int gamePos = static_cast<int>(std::distance(siblings.cbegin(), gameIter));
if (gameIter != siblings.cend()) {
if ((gamePos + 1) < static_cast<int>(siblings.size()))
setCursor(siblings.at(gamePos + 1));
else if ((gamePos - 1) > 0)
setCursor(siblings.at(gamePos - 1));
}
}
mGrid.remove(game);
if (mGrid.size() == 0)
addPlaceholder();
// If a game has been deleted, immediately remove the entry from gamelist.xml
// regardless of the value of the setting SaveGamelistsMode.
game->setDeletionFlag(true);
parent->getSystem()->writeMetaData();
// Remove before repopulating (removes from parent), then update the view.
delete game;
onFileChanged(parent, false);
}
void GridGamelistView::removeMedia(FileData* game)
{
std::string systemMediaDir = FileData::getMediaDirectory() + game->getSystem()->getName();
std::string mediaType;
std::string path;
// If there are no media files left in the directory after the deletion, then remove
// the directory too. Remove any empty parent directories as well.
auto removeEmptyDirFunc = [](std::string systemMediaDir, std::string mediaType,
std::string path) {
std::string parentPath = Utils::FileSystem::getParent(path);
while (parentPath != systemMediaDir + "/" + mediaType) {
if (Utils::FileSystem::getDirContent(parentPath).size() == 0) {
Utils::FileSystem::removeDirectory(parentPath);
parentPath = Utils::FileSystem::getParent(parentPath);
}
else {
break;
}
}
};
// Remove all game media files on the filesystem.
while (Utils::FileSystem::exists(game->getVideoPath())) {
mediaType = "videos";
path = game->getVideoPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getMiximagePath())) {
mediaType = "miximages";
path = game->getMiximagePath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getScreenshotPath())) {
mediaType = "screenshots";
path = game->getScreenshotPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getTitleScreenPath())) {
mediaType = "titlescreens";
path = game->getTitleScreenPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getCoverPath())) {
mediaType = "covers";
path = game->getCoverPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
if (Utils::FileSystem::exists(game->getBackCoverPath())) {
mediaType = "backcovers";
path = game->getBackCoverPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getFanArtPath())) {
mediaType = "fanart";
path = game->getFanArtPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getMarqueePath())) {
mediaType = "marquees";
path = game->getMarqueePath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->get3DBoxPath())) {
mediaType = "3dboxes";
path = game->get3DBoxPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getPhysicalMediaPath())) {
mediaType = "physicalmedia";
path = game->getPhysicalMediaPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
while (Utils::FileSystem::exists(game->getThumbnailPath())) {
mediaType = "thumbnails";
path = game->getThumbnailPath();
Utils::FileSystem::removeFile(path);
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
}
std::vector<TextComponent*> GridGamelistView::getMDLabels()
{
std::vector<TextComponent*> ret;
ret.push_back(&mLblRating);
ret.push_back(&mLblReleaseDate);
ret.push_back(&mLblDeveloper);
ret.push_back(&mLblPublisher);
ret.push_back(&mLblGenre);
ret.push_back(&mLblPlayers);
ret.push_back(&mLblLastPlayed);
ret.push_back(&mLblPlayCount);
return ret;
}
std::vector<GuiComponent*> GridGamelistView::getMDValues()
{
std::vector<GuiComponent*> ret;
ret.push_back(&mRating);
ret.push_back(&mReleaseDate);
ret.push_back(&mDeveloper);
ret.push_back(&mPublisher);
ret.push_back(&mGenre);
ret.push_back(&mPlayers);
ret.push_back(&mLastPlayed);
ret.push_back(&mPlayCount);
return ret;
}
std::vector<HelpPrompt> GridGamelistView::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
if (Settings::getInstance()->getBool("QuickSystemSelect"))
prompts.push_back(HelpPrompt("lr", "system"));
prompts.push_back(HelpPrompt("up/down/left/right", "choose"));
if (mRoot->getSystem()->getThemeFolder() == "custom-collections" && mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing == ViewController::GAMELIST)
prompts.push_back(HelpPrompt("a", "enter"));
else
prompts.push_back(HelpPrompt("a", "launch"));
prompts.push_back(HelpPrompt("b", "back"));
if (mRoot->getSystem()->isGameSystem() &&
mRoot->getSystem()->getThemeFolder() != "custom-collections")
prompts.push_back(HelpPrompt("x", "view media"));
if (mRoot->getSystem()->isGameSystem() && !mCursorStack.empty() &&
mRoot->getSystem()->getThemeFolder() == "custom-collections")
prompts.push_back(HelpPrompt("x", "view media"));
if (!UIModeController::getInstance()->isUIModeKid())
prompts.push_back(HelpPrompt("back", "options"));
if (mRoot->getSystem()->isGameSystem() && Settings::getInstance()->getBool("RandomAddButton"))
prompts.push_back(HelpPrompt("thumbstickclick", "random"));
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();
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();
prompts.push_back(HelpPrompt("y", prompt));
}
return prompts;
}
void GridGamelistView::update(int deltaTime)
{
// Update.
ISimpleGamelistView::update(deltaTime);
}

View file

@ -1,108 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GridGamelistView.h
//
// Interface that defines a GamelistView of the type 'grid'.
//
#ifndef ES_APP_VIEWS_GAMELIST_GRID_GAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_GRID_GAMELIST_VIEW_H
#include "components/BadgeComponent.h"
#include "components/DateTimeComponent.h"
#include "components/ImageGridComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "components/VideoComponent.h"
#include "views/gamelist/ISimpleGamelistView.h"
class GridGamelistView : public ISimpleGamelistView
{
public:
GridGamelistView(Window* window, FileData* root);
virtual ~GridGamelistView() {}
// Called when a FileData* is added, has its metadata changed, or is removed.
void onFileChanged(FileData* file, bool reloadGamelist) override;
void onShow() override;
void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
void setCursor(FileData* cursor) override;
FileData* getCursor() override { return mGrid.getSelected(); }
FileData* getNextEntry() override { return mGrid.getNext(); }
FileData* getPreviousEntry() override { return mGrid.getPrevious(); }
FileData* getFirstEntry() override { return mGrid.getFirst(); }
FileData* getLastEntry() override { return mGrid.getLast(); }
FileData* getFirstGameEntry() override { return firstGameEntry; }
std::string getName() const override { return "grid"; }
bool input(InputConfig* config, Input input) override;
std::vector<HelpPrompt> getHelpPrompts() override;
void launch(FileData* game) override;
bool isListScrolling() override { return mGrid.isScrolling(); }
void stopListScrolling() override
{
mGrid.stopAllAnimations();
mGrid.stopScrolling();
}
const std::vector<std::string>& getFirstLetterIndex() override { return mFirstLetterIndex; }
void addPlaceholder(FileData* firstEntry = nullptr) override;
protected:
std::string getQuickSystemSelectRightButton() override { return "rightshoulder"; }
std::string getQuickSystemSelectLeftButton() override { return "leftshoulder"; }
void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override;
void remove(FileData* game, bool deleteFile) override;
void removeMedia(FileData* game) override;
void update(int deltaTime) override;
ImageGridComponent<FileData*> mGrid;
// Points to the first game in the list, i.e. the first entry which is of the type 'GAME'.
FileData* firstGameEntry;
private:
void updateInfoPanel();
const std::string getImagePath(FileData* file);
void initMDLabels();
void initMDValues();
ImageComponent mMarquee;
ImageComponent mImage;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
BadgeComponent mBadges;
RatingComponent mRating;
DateTimeComponent mReleaseDate;
TextComponent mDeveloper;
TextComponent mPublisher;
TextComponent mGenre;
TextComponent mPlayers;
DateTimeComponent mLastPlayed;
TextComponent mPlayCount;
TextComponent mName;
std::vector<TextComponent*> getMDLabels();
std::vector<GuiComponent*> getMDValues();
ScrollableContainer mDescContainer;
TextComponent mDescription;
TextComponent mGamelistInfo;
};
#endif // ES_APP_VIEWS_GAMELIST_GRID_GAMELIST_VIEW_H

View file

@ -1,77 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// IGamelistView.cpp
//
// Interface that defines the minimum for a GamelistView.
//
#include "views/gamelist/IGamelistView.h"
#include "Sound.h"
#include "UIModeController.h"
#include "Window.h"
#include "guis/GuiGamelistOptions.h"
#include "views/ViewController.h"
IGamelistView::IGamelistView(Window* window, FileData* root)
: GuiComponent {window}
, mRoot {root}
{
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
}
void IGamelistView::setTheme(const std::shared_ptr<ThemeData>& theme)
{
mTheme = theme;
onThemeChanged(theme);
}
bool IGamelistView::input(InputConfig* config, Input input)
{
// Select button opens GuiGamelistOptions.
if (!UIModeController::getInstance()->isUIModeKid() && // Line break.
config->isMappedTo("back", input) && input.value) {
ViewController::getInstance()->cancelViewTransitions();
stopListScrolling();
mWindow->pushGui(new GuiGamelistOptions(mWindow, this->mRoot->getSystem()));
return true;
}
// Ctrl-R reloads the view when debugging.
else if (Settings::getInstance()->getBool("Debug") &&
config->getDeviceId() == DEVICE_KEYBOARD &&
(SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) && input.id == SDLK_r &&
input.value != 0) {
LOG(LogDebug) << "IGamelistView::input(): Reloading view";
ViewController::getInstance()->reloadGamelistView(this, true);
return true;
}
return GuiComponent::input(config, input);
}
HelpStyle IGamelistView::getHelpStyle()
{
HelpStyle style;
style.applyTheme(mTheme, getName());
return style;
}
void IGamelistView::render(const glm::mat4& parentTrans)
{
glm::mat4 trans {parentTrans * getTransform()};
float scaleX = trans[0].x;
float scaleY = trans[1].y;
glm::ivec2 pos {static_cast<int>(std::round(trans[3].x)),
static_cast<int>(std::round(trans[3].y))};
glm::ivec2 size {static_cast<int>(std::round(mSize.x * scaleX)),
static_cast<int>(std::round(mSize.y * scaleY))};
Renderer::pushClipRect(pos, size);
renderChildren(trans);
Renderer::popClipRect();
}

View file

@ -1,66 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// IGamelistView.h
//
// Interface that defines the minimum for a GamelistView.
//
#ifndef ES_APP_VIEWS_GAMELIST_IGAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_IGAMELIST_VIEW_H
#include "FileData.h"
#include "GuiComponent.h"
#include "renderers/Renderer.h"
class ThemeData;
class Window;
// This is an interface that defines the minimum for a GamelistView.
class IGamelistView : public GuiComponent
{
public:
IGamelistView(Window* window, FileData* root);
virtual ~IGamelistView() {}
// Called when a FileData* is added, has its metadata changed, or is removed.
virtual void onFileChanged(FileData* file, bool reloadGamelist) = 0;
// Called whenever the theme changes.
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) = 0;
void setTheme(const std::shared_ptr<ThemeData>& theme);
const std::shared_ptr<ThemeData> getTheme() const { return mTheme; }
virtual void preloadGamelist() {};
virtual FileData* getCursor() = 0;
virtual void setCursor(FileData*) = 0;
virtual FileData* getNextEntry() = 0;
virtual FileData* getPreviousEntry() = 0;
virtual FileData* getFirstEntry() = 0;
virtual FileData* getLastEntry() = 0;
virtual FileData* getFirstGameEntry() = 0;
virtual const std::vector<std::string>& getFirstLetterIndex() = 0;
virtual void addPlaceholder(FileData*) = 0;
virtual void copyCursorHistory(std::vector<FileData*>& cursorHistory) = 0;
virtual void populateCursorHistory(std::vector<FileData*>& cursorHistory) = 0;
bool input(InputConfig* config, Input input) override;
virtual void remove(FileData* game, bool deleteFile) = 0;
virtual void removeMedia(FileData* game) = 0;
virtual std::string getName() const = 0;
virtual void launch(FileData* game) = 0;
HelpStyle getHelpStyle() override;
void render(const glm::mat4& parentTrans) override;
protected:
FileData* mRoot;
std::shared_ptr<ThemeData> mTheme;
};
#endif // ES_APP_VIEWS_GAMELIST_IGAMELIST_VIEW_H

View file

@ -1,583 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// ISimpleGamelistView.cpp
//
// Interface that defines a simple gamelist view.
//
#include "views/gamelist/ISimpleGamelistView.h"
#include "CollectionSystemsManager.h"
#include "FileFilterIndex.h"
#include "Settings.h"
#include "Sound.h"
#include "SystemData.h"
#include "UIModeController.h"
#include "Window.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include "Log.h"
ISimpleGamelistView::ISimpleGamelistView(Window* window, FileData* root)
: IGamelistView {window, root}
, mHeaderText {window}
, mHeaderImage {window}
, mBackground {window}
, mRandomGame {nullptr}
{
mHeaderText.setText("Logo Text", false);
mHeaderText.setSize(mSize.x, 0.0f);
mHeaderText.setPosition(0.0f, 0.0f);
mHeaderText.setHorizontalAlignment(ALIGN_CENTER);
mHeaderText.setDefaultZIndex(50.0f);
mHeaderImage.setResize(0.0f, mSize.y * 0.185f);
mHeaderImage.setOrigin(0.5f, 0.0f);
mHeaderImage.setPosition(mSize.x / 2.0f, 0.0f);
mHeaderImage.setDefaultZIndex(50.0f);
mBackground.setResize(mSize.x, mSize.y);
mBackground.setDefaultZIndex(0.0f);
addChild(&mHeaderText);
addChild(&mBackground);
}
ISimpleGamelistView::~ISimpleGamelistView()
{
// Remove theme extras.
for (auto extra : mThemeExtras) {
removeChild(extra);
delete extra;
}
mThemeExtras.clear();
}
void ISimpleGamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
using namespace ThemeFlags;
mBackground.applyTheme(theme, getName(), "background", ALL);
mHeaderImage.applyTheme(theme, getName(), "logo", ALL);
mHeaderText.applyTheme(theme, getName(), "logoText", ALL);
// Remove old theme extras.
for (auto extra : mThemeExtras) {
removeChild(extra);
delete extra;
}
mThemeExtras.clear();
// Add new theme extras.
mThemeExtras = ThemeData::makeExtras(theme, getName(), mWindow);
for (auto extra : mThemeExtras)
addChild(extra);
if (mHeaderImage.hasImage()) {
removeChild(&mHeaderText);
addChild(&mHeaderImage);
}
else {
addChild(&mHeaderText);
removeChild(&mHeaderImage);
}
}
void ISimpleGamelistView::onFileChanged(FileData* file, bool reloadGamelist)
{
// 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());
setCursor(cursor);
}
else {
populateList(mRoot->getChildrenListToDisplay(), mRoot);
setCursor(cursor);
}
}
bool ISimpleGamelistView::input(InputConfig* config, Input input)
{
if (input.value != 0) {
if (config->isMappedTo("a", input)) {
FileData* cursor = getCursor();
if (cursor->getType() == GAME) {
onPauseVideo();
ViewController::getInstance()->cancelViewTransitions();
stopListScrolling();
launch(cursor);
}
else {
// It's a folder.
if (cursor->getChildren().size() > 0) {
ViewController::getInstance()->cancelViewTransitions();
NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
mCursorStack.push(cursor);
populateList(cursor->getChildrenListToDisplay(), cursor);
FileData* newCursor = nullptr;
std::vector<FileData*> listEntries = cursor->getChildrenListToDisplay();
// Check if there is an entry in the cursor stack history matching any entry
// in the currect folder. If so, select that entry.
for (auto it = mCursorStackHistory.begin(); // Line break.
it != mCursorStackHistory.end(); ++it) {
if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) {
newCursor = *it;
mCursorStackHistory.erase(it);
break;
}
}
// If there was no match in the cursor history, simply select the first entry.
if (!newCursor)
newCursor = getCursor();
setCursor(newCursor);
if (mRoot->getSystem()->getThemeFolder() == "custom-collections")
updateHelpPrompts();
}
else {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
}
return true;
}
else if (config->isMappedTo("b", input)) {
ViewController::getInstance()->cancelViewTransitions();
if (mCursorStack.size()) {
// Save the position to the cursor stack history.
mCursorStackHistory.push_back(getCursor());
NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
populateList(mCursorStack.top()->getParent()->getChildrenListToDisplay(),
mCursorStack.top()->getParent());
setCursor(mCursorStack.top());
if (mCursorStack.size() > 0)
mCursorStack.pop();
if (mRoot->getSystem()->getThemeFolder() == "custom-collections")
updateHelpPrompts();
}
else {
NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
onPauseVideo();
onFocusLost();
stopListScrolling();
SystemData* systemToView = getCursor()->getSystem();
if (systemToView->isCustomCollection() &&
systemToView->getRootFolder()->getParent())
ViewController::getInstance()->goToSystemView(
systemToView->getRootFolder()->getParent()->getSystem(), true);
else
ViewController::getInstance()->goToSystemView(systemToView, true);
}
return true;
}
else if (config->isMappedTo("x", input)) {
if (getCursor()->getType() == PLACEHOLDER) {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
return true;
}
else if (config->isMappedTo("x", input) &&
mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing ==
ViewController::GAMELIST) {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
// Jump to the randomly selected game.
if (mRandomGame) {
stopListScrolling();
ViewController::getInstance()->cancelViewTransitions();
mWindow->startMediaViewer(mRandomGame);
return true;
}
}
else if (mRoot->getSystem()->isGameSystem()) {
stopListScrolling();
ViewController::getInstance()->cancelViewTransitions();
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mWindow->startMediaViewer(getCursor());
return true;
}
}
else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
onPauseVideo();
onFocusLost();
stopListScrolling();
ViewController::getInstance()->goToNextGamelist();
return true;
}
}
else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
onPauseVideo();
onFocusLost();
stopListScrolling();
ViewController::getInstance()->goToPrevGamelist();
return true;
}
}
else if (Settings::getInstance()->getBool("RandomAddButton") &&
(config->isMappedTo("leftthumbstickclick", input) ||
config->isMappedTo("rightthumbstickclick", input))) {
if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER) {
stopListScrolling();
// Jump to a random game.
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor());
if (randomGame)
setCursor(randomGame);
return true;
}
}
else if (config->isMappedTo("y", input) &&
mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
!CollectionSystemsManager::getInstance()->isEditing() && mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing == ViewController::GAMELIST) {
// Jump to the randomly selected game.
if (mRandomGame) {
NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
// If there is already an mCursorStackHistory entry for the collection, then
// remove it so we don't get multiple entries.
std::vector<FileData*> listEntries =
mRandomGame->getSystem()->getRootFolder()->getChildrenListToDisplay();
for (auto it = mCursorStackHistory.begin(); it != mCursorStackHistory.end(); ++it) {
if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) {
mCursorStackHistory.erase(it);
break;
}
}
setCursor(mRandomGame);
updateHelpPrompts();
}
else {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
}
else if (config->isMappedTo("y", input) &&
!Settings::getInstance()->getBool("FavoritesAddButton") &&
!CollectionSystemsManager::getInstance()->isEditing()) {
return true;
}
else if (config->isMappedTo("y", input) &&
!UIModeController::getInstance()->isUIModeKid() &&
!UIModeController::getInstance()->isUIModeKiosk()) {
// Notify the user if attempting to add a custom collection to a custom collection.
if (CollectionSystemsManager::getInstance()->isEditing() &&
mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER &&
getCursor()->getParent()->getPath() == "collections") {
NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND);
mWindow->queueInfoPopup("CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS", 4000);
}
// Notify the user if attempting to add a placeholder to a custom collection.
if (CollectionSystemsManager::getInstance()->isEditing() &&
mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) {
NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND);
mWindow->queueInfoPopup("CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000);
}
else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER &&
getCursor()->getParent()->getPath() != "collections") {
if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER)
NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND);
// When marking or unmarking a game as favorite, don't jump to the new position
// it gets after the gamelist sorting. Instead retain the cursor position in the
// list using the logic below.
FileData* entryToUpdate = getCursor();
SystemData* system = getCursor()->getSystem();
bool favoritesSorting;
bool removedLastFavorite = false;
bool selectLastEntry = false;
bool isEditing = CollectionSystemsManager::getInstance()->isEditing();
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
// If the current list only contains folders, then treat it as if the folders
// are not sorted on top, this way the logic should work exactly as for mixed
// lists or files-only lists.
if (getCursor()->getType() == FOLDER && foldersOnTop == true)
foldersOnTop = !getCursor()->getParent()->getOnlyFoldersFlag();
if (mRoot->getSystem()->isCustomCollection() ||
mRoot->getSystem()->getThemeFolder() == "custom-collections")
favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom");
else
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (favoritesSorting && mRoot->getSystem()->getName() != "recent" && !isEditing) {
FileData* entryToSelect;
// Add favorite flag.
if (!getCursor()->getFavorite()) {
// If it's a folder and folders are sorted on top, select the current entry.
if (foldersOnTop && getCursor()->getType() == FOLDER) {
entryToSelect = getCursor();
}
// If it's the first entry to be marked as favorite, select the next entry.
else if (getCursor() == getFirstEntry()) {
entryToSelect = getNextEntry();
}
else if (getCursor() == getLastEntry() &&
getPreviousEntry()->getFavorite()) {
entryToSelect = getLastEntry();
selectLastEntry = true;
}
// If we are on the favorite marking boundary, select the next entry.
else if (getCursor()->getFavorite() != getPreviousEntry()->getFavorite()) {
entryToSelect = getNextEntry();
}
// If we mark the second entry as favorite and the first entry is not a
// favorite, then select this entry if they are of the same type.
else if (getPreviousEntry() == getFirstEntry() &&
getCursor()->getType() == getPreviousEntry()->getType()) {
entryToSelect = getPreviousEntry();
}
// For all other scenarios try to select the next entry, and if it doesn't
// exist, select the previous entry.
else {
entryToSelect =
getCursor() != getNextEntry() ? getNextEntry() : getPreviousEntry();
}
}
// Remove favorite flag.
else {
// If it's a folder and folders are sorted on top, select the current entry.
if (foldersOnTop && getCursor()->getType() == FOLDER) {
entryToSelect = getCursor();
}
// If it's the last entry, select the previous entry.
else if (getCursor() == getLastEntry()) {
entryToSelect = getPreviousEntry();
}
// If we are on the favorite marking boundary, select the previous entry,
// unless folders are sorted on top and the previous entry is a folder.
else if (foldersOnTop &&
getCursor()->getFavorite() != getNextEntry()->getFavorite()) {
entryToSelect = getPreviousEntry()->getType() == FOLDER ?
getCursor() :
getPreviousEntry();
}
// If we are on the favorite marking boundary, select the previous entry.
else if (getCursor()->getFavorite() != getNextEntry()->getFavorite()) {
entryToSelect = getPreviousEntry();
}
// For all other scenarios try to select the next entry, and if it doesn't
// exist, select the previous entry.
else {
entryToSelect =
getCursor() != getNextEntry() ? getNextEntry() : getPreviousEntry();
}
// If we removed the last favorite marking, set the flag to jump to the
// first list entry after the sorting has been performed.
if (foldersOnTop && getCursor() == getFirstGameEntry() &&
!getNextEntry()->getFavorite())
removedLastFavorite = true;
else if (getCursor() == getFirstEntry() && !getNextEntry()->getFavorite())
removedLastFavorite = true;
}
setCursor(entryToSelect);
system = entryToUpdate->getSystem();
}
// Marking folders as favorites don't make them part of any collections,
// so it makes more sense to handle it here than to add the function to
// CollectionSystemsManager.
if (entryToUpdate->getType() == FOLDER) {
if (isEditing) {
mWindow->queueInfoPopup("CAN'T ADD FOLDERS TO CUSTOM COLLECTIONS", 4000);
}
else {
MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata;
if (md->get("favorite") == "false") {
md->set("favorite", "true");
mWindow->queueInfoPopup(
"MARKED FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) +
"' AS FAVORITE",
4000);
}
else {
md->set("favorite", "false");
mWindow->queueInfoPopup(
"REMOVED FAVORITE MARKING FOR FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) +
"'",
4000);
}
}
entryToUpdate->getSourceFileData()->getSystem()->onMetaDataSavePoint();
getCursor()->getParent()->sort(
mRoot->getSortTypeFromString(mRoot->getSortTypeString()),
Settings::getInstance()->getBool("FavoritesFirst"));
ViewController::getInstance()->onFileChanged(getCursor(), false);
// Always jump to the first entry in the gamelist if the last favorite
// was unmarked. We couldn't do this earlier as we didn't have the list
// sorted yet.
if (removedLastFavorite) {
ViewController::getInstance()
->getGamelistView(entryToUpdate->getSystem())
->setCursor(ViewController::getInstance()
->getGamelistView(entryToUpdate->getSystem())
->getFirstEntry());
}
return true;
}
else if (isEditing && entryToUpdate->metadata.get("nogamecount") == "true") {
mWindow->queueInfoPopup("CAN'T ADD ENTRIES THAT ARE NOT COUNTED "
"AS GAMES TO CUSTOM COLLECTIONS",
4000);
}
else if (CollectionSystemsManager::getInstance()->toggleGameInCollection(
entryToUpdate)) {
// As the toggling of the game destroyed this object, we need to get the view
// from ViewController instead of using the reference that existed before the
// destruction. Otherwise we get random crashes.
IGamelistView* view =
ViewController::getInstance()->getGamelistView(system).get();
// Jump to the first entry in the gamelist if the last favorite was unmarked.
if (foldersOnTop && removedLastFavorite &&
!entryToUpdate->getSystem()->isCustomCollection()) {
ViewController::getInstance()
->getGamelistView(entryToUpdate->getSystem())
->setCursor(ViewController::getInstance()
->getGamelistView(entryToUpdate->getSystem())
->getFirstGameEntry());
}
else if (removedLastFavorite &&
!entryToUpdate->getSystem()->isCustomCollection()) {
view->setCursor(view->getFirstEntry());
}
else if (selectLastEntry) {
view->setCursor(view->getLastEntry());
}
// Display the indication icons which show what games are part of the
// custom collection currently being edited. This is done cheaply using
// onFileChanged() which will trigger populateList().
if (isEditing) {
for (auto it = SystemData::sSystemVector.begin();
it != SystemData::sSystemVector.end(); ++it) {
ViewController::getInstance()->getGamelistView((*it))->onFileChanged(
ViewController::getInstance()->getGamelistView((*it))->getCursor(),
false);
}
}
return true;
}
}
else if (config->isMappedTo("y", input) && getCursor()->isPlaceHolder()) {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
}
}
return IGamelistView::input(config, input);
}
void ISimpleGamelistView::generateGamelistInfo(FileData* cursor, FileData* firstEntry)
{
// Generate data needed for the gamelistInfo field, which is displayed from the
// gamelist interfaces (Detailed/Video/Grid).
mIsFiltered = false;
mIsFolder = false;
FileData* rootFolder = firstEntry->getSystem()->getRootFolder();
std::pair<unsigned int, unsigned int> gameCount;
FileFilterIndex* idx = rootFolder->getSystem()->getIndex();
// For the 'recent' collection we need to recount the games as the collection was
// trimmed down to 50 items. If we don't do this, the game count will not be correct
// as it would include all the games prior to trimming.
if (mRoot->getPath() == "recent")
mRoot->countGames(gameCount);
gameCount = rootFolder->getGameCount();
mGameCount = gameCount.first;
mFavoritesGameCount = gameCount.second;
mFilteredGameCount = 0;
mFilteredGameCountAll = 0;
if (idx->isFiltered()) {
mIsFiltered = true;
mFilteredGameCount =
static_cast<unsigned int>(rootFolder->getFilesRecursive(GAME, true, false).size());
// Also count the games that are set to not be counted as games, as the filter may
// apply to such entries as well and this will be indicated with a separate '+ XX'
// in the GamelistInfo field.
mFilteredGameCountAll =
static_cast<unsigned int>(rootFolder->getFilesRecursive(GAME, true, true).size());
}
if (firstEntry->getParent() && firstEntry->getParent()->getType() == FOLDER)
mIsFolder = true;
}
void ISimpleGamelistView::generateFirstLetterIndex(const std::vector<FileData*>& files)
{
std::string firstChar;
bool onlyFavorites = true;
bool onlyFolders = true;
bool hasFavorites = false;
bool hasFolders = false;
bool favoritesSorting = false;
mFirstLetterIndex.clear();
if (files.size() > 0 && files.front()->getSystem()->isCustomCollection())
favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom");
else
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
// Find out if there are only favorites and/or only folders in the list.
for (auto it = files.begin(); it != files.end(); ++it) {
if (!((*it)->getFavorite()))
onlyFavorites = false;
if (!((*it)->getType() == FOLDER))
onlyFolders = false;
}
// Build the index.
for (auto it = files.begin(); it != files.end(); ++it) {
if ((*it)->getType() == FOLDER && (*it)->getFavorite() && favoritesSorting &&
!onlyFavorites) {
hasFavorites = true;
}
else if ((*it)->getType() == FOLDER && foldersOnTop && !onlyFolders) {
hasFolders = true;
}
else if ((*it)->getType() == GAME && (*it)->getFavorite() && favoritesSorting &&
!onlyFavorites) {
hasFavorites = true;
}
else {
mFirstLetterIndex.push_back(Utils::String::getFirstCharacter((*it)->getSortName()));
}
}
// Sort and make each entry unique.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
mFirstLetterIndex.erase(last, mFirstLetterIndex.end());
// If there are any favorites and/or folders in the list, insert their respective
// Unicode characters at the beginning of the vector.
if (hasFavorites)
mFirstLetterIndex.insert(mFirstLetterIndex.begin(), ViewController::FAVORITE_CHAR);
if (hasFolders)
mFirstLetterIndex.insert(mFirstLetterIndex.begin(), ViewController::FOLDER_CHAR);
}

View file

@ -1,78 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// ISimpleGamelistView.h
//
// Interface that defines a simple gamelist view.
//
#ifndef ES_APP_VIEWS_GAMELIST_ISIMPLE_GAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_ISIMPLE_GAMELIST_VIEW_H
#include "components/ImageComponent.h"
#include "components/TextComponent.h"
#include "views/gamelist/IGamelistView.h"
#include <stack>
class ISimpleGamelistView : public IGamelistView
{
public:
ISimpleGamelistView(Window* window, FileData* root);
virtual ~ISimpleGamelistView();
// Called when a FileData* is added, has its metadata changed, or is removed.
void onFileChanged(FileData* file, bool reloadGamelist) override;
// Called whenever the theme changes.
void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() override = 0;
virtual void setCursor(FileData*) override = 0;
virtual void addPlaceholder(FileData*) override = 0;
bool input(InputConfig* config, Input input) override;
virtual void launch(FileData* game) override = 0;
virtual const std::vector<std::string>& getFirstLetterIndex() override = 0;
// These functions are used to retain the folder cursor history, for instance
// during a view reload. The calling function stores the history temporarily.
void copyCursorHistory(std::vector<FileData*>& cursorHistory) override
{
cursorHistory = mCursorStackHistory;
};
void populateCursorHistory(std::vector<FileData*>& cursorHistory) override
{
mCursorStackHistory = cursorHistory;
};
protected:
virtual std::string getQuickSystemSelectRightButton() = 0;
virtual std::string getQuickSystemSelectLeftButton() = 0;
virtual void populateList(const std::vector<FileData*>& files, FileData* firstEntry) = 0;
void generateGamelistInfo(FileData* cursor, FileData* firstEntry);
void generateFirstLetterIndex(const std::vector<FileData*>& files);
TextComponent mHeaderText;
ImageComponent mHeaderImage;
ImageComponent mBackground;
std::vector<GuiComponent*> mThemeExtras;
std::stack<FileData*> mCursorStack;
std::vector<FileData*> mCursorStackHistory;
// This game is randomly selected in the grouped custom collections view.
FileData* mRandomGame;
std::vector<std::string> mFirstLetterIndex;
unsigned int mGameCount;
unsigned int mFavoritesGameCount;
unsigned int mFilteredGameCount;
unsigned int mFilteredGameCountAll;
bool mIsFiltered;
bool mIsFolder;
};
#endif // ES_APP_VIEWS_GAMELIST_ISIMPLE_GAMELIST_VIEW_H

View file

@ -1,561 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// VideoGamelistView.cpp
//
// Interface that defines a GamelistView of the type 'video'.
//
#include "views/gamelist/VideoGamelistView.h"
#include "CollectionSystemsManager.h"
#include "SystemData.h"
#include "animations/LambdaAnimation.h"
#include "components/VideoFFmpegComponent.h"
#include "utils/FileSystemUtil.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650
VideoGamelistView::VideoGamelistView(Window* window, FileData* root)
: BasicGamelistView(window, root)
, mThumbnail(window)
, mMarquee(window)
, mImage(window)
, mVideo(nullptr)
, mLblRating(window)
, mLblReleaseDate(window)
, mLblDeveloper(window)
, mLblPublisher(window)
, mLblGenre(window)
, mLblPlayers(window)
, mLblLastPlayed(window)
, mLblPlayCount(window)
, mRating(window)
, mReleaseDate(window)
, mDeveloper(window)
, mPublisher(window)
, mGenre(window)
, mPlayers(window)
, mLastPlayed(window)
, mPlayCount(window)
, mName(window)
, mBadges(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mVideoPlaying(false)
, mLastUpdated(nullptr)
{
const float padding = 0.01f;
// Create the video window.
mVideo = new VideoFFmpegComponent(window);
mList.setPosition(mSize.x * (0.50f + padding), mList.getPosition().y);
mList.setSize(mSize.x * (0.50f - padding), mList.getSize().y);
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
// Thumbnail.
mThumbnail.setOrigin(0.5f, 0.5f);
mThumbnail.setPosition(2.0f, 2.0f);
mThumbnail.setVisible(false);
mThumbnail.setMaxSize(mSize.x * (0.25f - 2.0f * padding), mSize.y * 0.10f);
mThumbnail.setDefaultZIndex(35.0f);
addChild(&mThumbnail);
// Marquee.
mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x * 0.25f, mSize.y * 0.10f);
mMarquee.setMaxSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.18f);
mMarquee.setDefaultZIndex(35.0f);
addChild(&mMarquee);
// Video.
mVideo->setOrigin(0.5f, 0.5f);
mVideo->setPosition(mSize.x * 0.25f, mSize.y * 0.4f);
mVideo->setSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.4f);
mVideo->setDefaultZIndex(30.0f);
addChild(mVideo);
// Metadata labels + values.
mLblRating.setText("Rating: ", false);
addChild(&mLblRating);
addChild(&mRating);
mLblReleaseDate.setText("Released: ", false);
addChild(&mLblReleaseDate);
addChild(&mReleaseDate);
mLblDeveloper.setText("Developer: ", false);
addChild(&mLblDeveloper);
addChild(&mDeveloper);
mLblPublisher.setText("Publisher: ", false);
addChild(&mLblPublisher);
addChild(&mPublisher);
mLblGenre.setText("Genre: ", false);
addChild(&mLblGenre);
addChild(&mGenre);
mLblPlayers.setText("Players: ", false);
addChild(&mLblPlayers);
addChild(&mPlayers);
mLblLastPlayed.setText("Last played: ", false);
addChild(&mLblLastPlayed);
mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: ", false);
addChild(&mLblPlayCount);
addChild(&mPlayCount);
// Badges.
addChild(&mBadges);
mBadges.setOrigin(0.5f, 0.5f);
mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f);
mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f);
mBadges.setDefaultZIndex(50.0f);
mName.setPosition(mSize.x, mSize.y);
mName.setDefaultZIndex(40.0f);
mName.setColor(0xAAAAAAFF);
mName.setFont(Font::get(FONT_SIZE_MEDIUM));
mName.setHorizontalAlignment(ALIGN_CENTER);
addChild(&mName);
mDescContainer.setPosition(mSize.x * padding, mSize.y * 0.65f);
mDescContainer.setSize(mSize.x * (0.50f - 2.0f * padding),
mSize.y - mDescContainer.getPosition().y);
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40.0f);
addChild(&mDescContainer);
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescContainer.addChild(&mDescription);
mGamelistInfo.setOrigin(0.5f, 0.5f);
mGamelistInfo.setFont(Font::get(FONT_SIZE_SMALL));
mGamelistInfo.setDefaultZIndex(50.0f);
mGamelistInfo.setVisible(true);
addChild(&mGamelistInfo);
initMDLabels();
initMDValues();
}
VideoGamelistView::~VideoGamelistView() { delete mVideo; }
void VideoGamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
BasicGamelistView::onThemeChanged(theme);
using namespace ThemeFlags;
mThumbnail.applyTheme(theme, getName(), "md_thumbnail",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video",
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION |
VISIBLE);
mName.applyTheme(theme, getName(), "md_name", ALL);
mBadges.applyTheme(theme, getName(), "md_badges", ALL);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8);
std::vector<std::string> lblElements = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"};
for (unsigned int i = 0; i < labels.size(); ++i)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8);
std::vector<std::string> valElements = {"md_rating", "md_releasedate", "md_developer",
"md_publisher", "md_genre", "md_players",
"md_lastplayed", "md_playcount"};
for (unsigned int i = 0; i < values.size(); ++i)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x, 0.0f);
mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT);
// If there is no position defined in the theme for gamelistInfo, then hide it.
if (mGamelistInfo.getPosition() == glm::vec3 {})
mGamelistInfo.setVisible(false);
else
mGamelistInfo.setVisible(true);
sortChildren();
}
void VideoGamelistView::initMDLabels()
{
std::vector<TextComponent*> components = getMDLabels();
const unsigned int colCount = 2;
const unsigned int rowCount = static_cast<int>(components.size() / 2);
glm::vec3 start {mSize.x * 0.01f, mSize.y * 0.625f, 0.0f};
const float colSize = (mSize.x * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y;
for (unsigned int i = 0; i < components.size(); ++i) {
const unsigned int row = i % rowCount;
glm::vec3 pos {};
if (row == 0) {
pos = start + glm::vec3 {colSize * (i / rowCount), 0.0f, 0.0f};
}
else {
// Work from the last component.
GuiComponent* lc = components[i - 1];
pos = lc->getPosition() + glm::vec3 {0.0f, lc->getSize().y + rowPadding, 0.0f};
}
components[i]->setFont(Font::get(FONT_SIZE_SMALL));
components[i]->setPosition(pos);
components[i]->setDefaultZIndex(40.0f);
}
}
void VideoGamelistView::initMDValues()
{
std::vector<TextComponent*> labels = getMDLabels();
std::vector<GuiComponent*> values = getMDValues();
std::shared_ptr<Font> defaultFont = Font::get(FONT_SIZE_SMALL);
mRating.setSize(defaultFont->getHeight() * 5.0f, static_cast<float>(defaultFont->getHeight()));
mReleaseDate.setFont(defaultFont);
mDeveloper.setFont(defaultFont);
mPublisher.setFont(defaultFont);
mGenre.setFont(defaultFont);
mPlayers.setFont(defaultFont);
mLastPlayed.setFont(defaultFont);
mPlayCount.setFont(defaultFont);
float bottom = 0.0f;
const float colSize = (mSize.x * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); ++i) {
const float heightDiff = (labels[i]->getSize().y - values[i]->getSize().y) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() +
glm::vec3 {labels[i]->getSize().x, heightDiff, 0.0f});
values[i]->setSize(colSize - labels[i]->getSize().x, values[i]->getSize().y);
values[i]->setDefaultZIndex(40.0f);
float testBot = values[i]->getPosition().y + values[i]->getSize().y;
if (testBot > bottom)
bottom = testBot;
}
mDescContainer.setPosition(mDescContainer.getPosition().x, bottom + mSize.y * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x, mSize.y - mDescContainer.getPosition().y);
}
void VideoGamelistView::updateInfoPanel()
{
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected();
// If the game data has already been rendered to the info panel, then skip it this time.
if (file == mLastUpdated)
return;
if (!mList.isScrolling())
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 (mList.isScrolling())
if ((mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true") ||
(mLastUpdated->getSystem()->isCustomCollection() &&
mLastUpdated->getPath() == mLastUpdated->getSystem()->getName()))
hideMetaDataFields = true;
if (hideMetaDataFields) {
mLblRating.setVisible(false);
mRating.setVisible(false);
mLblReleaseDate.setVisible(false);
mReleaseDate.setVisible(false);
mLblDeveloper.setVisible(false);
mDeveloper.setVisible(false);
mLblPublisher.setVisible(false);
mPublisher.setVisible(false);
mLblGenre.setVisible(false);
mGenre.setVisible(false);
mLblPlayers.setVisible(false);
mPlayers.setVisible(false);
mLblLastPlayed.setVisible(false);
mLastPlayed.setVisible(false);
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
mBadges.setVisible(false);
}
else {
mLblRating.setVisible(true);
mRating.setVisible(true);
mLblReleaseDate.setVisible(true);
mReleaseDate.setVisible(true);
mLblDeveloper.setVisible(true);
mDeveloper.setVisible(true);
mLblPublisher.setVisible(true);
mPublisher.setVisible(true);
mLblGenre.setVisible(true);
mGenre.setVisible(true);
mLblPlayers.setVisible(true);
mPlayers.setVisible(true);
mLblLastPlayed.setVisible(true);
mLastPlayed.setVisible(true);
mLblPlayCount.setVisible(true);
mPlayCount.setVisible(true);
mBadges.setVisible(true);
}
bool fadingOut = false;
if (file == nullptr) {
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) {
mThumbnail.setImage(mRandomGame->getThumbnailPath());
mMarquee.setImage(mRandomGame->getMarqueePath(), false, true);
mVideo->setImage(mRandomGame->getImagePath());
// Always stop the video before setting a new video as it will otherwise continue
// to play if it has the same path (i.e. it is the same physical video file) as
// the previously set video.
// That may happen when entering a folder with the same name as the first game
// file inside, or as in this case, when entering a custom collection.
mVideo->onHide();
if (!mVideo->setVideo(mRandomGame->getVideoPath()))
mVideo->setDefaultVideo();
}
else {
mThumbnail.setImage("");
mMarquee.setImage("");
mVideo->setImage("");
mVideo->setVideo("");
mVideo->setDefaultVideo();
}
}
else {
mThumbnail.setImage(file->getThumbnailPath());
mMarquee.setImage(file->getMarqueePath(), false, true);
mVideo->setImage(file->getImagePath());
mVideo->onHide();
if (!mVideo->setVideo(file->getVideoPath()))
mVideo->setDefaultVideo();
}
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.
std::string gamelistInfoString;
Alignment infoAlign = mGamelistInfo.getHorizontalAlignment();
if (mIsFolder && infoAlign == ALIGN_RIGHT)
gamelistInfoString = ViewController::FOLDER_CHAR + " ";
if (mIsFiltered) {
if (mFilteredGameCountAll == mFilteredGameCount)
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " / " +
std::to_string(mGameCount);
else
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
" / " + std::to_string(mGameCount);
}
else {
gamelistInfoString +=
ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
std::to_string(mFavoritesGameCount);
}
if (mIsFolder && infoAlign != ALIGN_RIGHT)
gamelistInfoString += " " + ViewController::FOLDER_CHAR;
mGamelistInfo.setValue(gamelistInfoString);
// Fade in the game image.
auto func = [this](float t) {
mVideo->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
};
mVideo->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
mDescription.setText(file->metadata.get("desc"));
mDescContainer.reset();
mRating.setValue(file->metadata.get("rating"));
mReleaseDate.setValue(file->metadata.get("releasedate"));
mDeveloper.setValue(file->metadata.get("developer"));
mPublisher.setValue(file->metadata.get("publisher"));
mGenre.setValue(file->metadata.get("genre"));
mPlayers.setValue(file->metadata.get("players"));
// Populate the badge slots based on game metadata.
std::vector<BadgeComponent::BadgeInfo> badgeSlots;
for (auto badge : mBadges.getBadgeTypes()) {
BadgeComponent::BadgeInfo badgeInfo;
badgeInfo.badgeType = badge;
if (badge == "controller") {
if (file->metadata.get("controller").compare("") != 0) {
badgeInfo.gameController = file->metadata.get("controller");
badgeSlots.push_back(badgeInfo);
}
}
else if (badge == "altemulator") {
if (file->metadata.get(badge).compare("") != 0)
badgeSlots.push_back(badgeInfo);
}
else {
if (file->metadata.get(badge).compare("true") == 0)
badgeSlots.push_back(badgeInfo);
}
}
mBadges.setBadges(badgeSlots);
mName.setValue(file->metadata.get("name"));
if (file->getType() == GAME) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
}
else if (file->getType() == FOLDER) {
if (!hideMetaDataFields) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mLblPlayCount.setVisible(false);
mPlayCount.setVisible(false);
}
}
fadingOut = false;
}
std::vector<GuiComponent*> comps = getMDValues();
comps.push_back(&mThumbnail);
comps.push_back(&mMarquee);
comps.push_back(mVideo);
comps.push_back(&mDescription);
comps.push_back(&mName);
comps.push_back(&mBadges);
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
comp->setAnimation(new LambdaAnimation(func, 200), 0, nullptr, fadingOut);
}
}
}
void VideoGamelistView::launch(FileData* game)
{
ViewController::getInstance()->triggerGameLaunch(game);
}
std::vector<TextComponent*> VideoGamelistView::getMDLabels()
{
std::vector<TextComponent*> ret;
ret.push_back(&mLblRating);
ret.push_back(&mLblReleaseDate);
ret.push_back(&mLblDeveloper);
ret.push_back(&mLblPublisher);
ret.push_back(&mLblGenre);
ret.push_back(&mLblPlayers);
ret.push_back(&mLblLastPlayed);
ret.push_back(&mLblPlayCount);
return ret;
}
std::vector<GuiComponent*> VideoGamelistView::getMDValues()
{
std::vector<GuiComponent*> ret;
ret.push_back(&mRating);
ret.push_back(&mReleaseDate);
ret.push_back(&mDeveloper);
ret.push_back(&mPublisher);
ret.push_back(&mGenre);
ret.push_back(&mPlayers);
ret.push_back(&mLastPlayed);
ret.push_back(&mPlayCount);
return ret;
}
void VideoGamelistView::update(int deltaTime)
{
if (!mVideoPlaying)
mVideo->onHide();
else if (mVideoPlaying && !mVideo->isVideoPaused() && !mWindow->isScreensaverActive())
mVideo->onShow();
BasicGamelistView::update(deltaTime);
mVideo->update(deltaTime);
if (ViewController::getInstance()->getGameLaunchTriggered() && mVideo->isAnimationPlaying(0))
mVideo->finishAnimation(0);
}
void VideoGamelistView::onShow()
{
// Reset any Lottie animations.
for (auto extra : mThemeExtras)
extra->resetFileAnimation();
mLastUpdated = nullptr;
GuiComponent::onShow();
updateInfoPanel();
}

View file

@ -1,78 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// VideoGamelistView.h
//
// Interface that defines a GamelistView of the type 'video'.
//
#ifndef ES_APP_VIEWS_GAMELIST_VIDEO_GAMELIST_VIEW_H
#define ES_APP_VIEWS_GAMELIST_VIDEO_GAMELIST_VIEW_H
#include "components/BadgeComponent.h"
#include "components/DateTimeComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "views/gamelist/BasicGamelistView.h"
class VideoComponent;
class VideoGamelistView : public BasicGamelistView
{
public:
VideoGamelistView(Window* window, FileData* root);
virtual ~VideoGamelistView();
void onShow() override;
void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
std::string getName() const override { return "video"; }
void launch(FileData* game) override;
void preloadGamelist() override { updateInfoPanel(); }
protected:
void update(int deltaTime) override;
private:
void updateInfoPanel();
void initMDLabels();
void initMDValues();
ImageComponent mThumbnail;
ImageComponent mMarquee;
ImageComponent mImage;
VideoComponent* mVideo;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
RatingComponent mRating;
DateTimeComponent mReleaseDate;
TextComponent mDeveloper;
TextComponent mPublisher;
TextComponent mGenre;
TextComponent mPlayers;
DateTimeComponent mLastPlayed;
TextComponent mPlayCount;
TextComponent mName;
BadgeComponent mBadges;
std::vector<TextComponent*> getMDLabels();
std::vector<GuiComponent*> getMDValues();
ScrollableContainer mDescContainer;
TextComponent mDescription;
TextComponent mGamelistInfo;
bool mVideoPlaying;
FileData* mLastUpdated;
};
#endif // ES_APP_VIEWS_GAMELIST_VIDEO_GAMELIST_VIEW_H

View file

@ -1,322 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GridTileComponent.cpp
//
// X*Y tile grid, used indirectly by GridGamelistView via ImageGridComponent.
//
#include "GridTileComponent.h"
#include "ThemeData.h"
#include "animations/LambdaAnimation.h"
#include "resources/TextureResource.h"
GridTileComponent::GridTileComponent()
: mBackground {":/graphics/frame.png"}
{
mDefaultProperties.mSize = getDefaultTileSize();
mDefaultProperties.mPadding = glm::vec2 {16.0f * Renderer::getScreenWidthModifier(),
16.0f * Renderer::getScreenHeightModifier()};
mDefaultProperties.mImageColor = 0xAAAAAABB;
// Attempting to use frame.svg instead causes quite severe performance problems.
mDefaultProperties.mBackgroundImage = ":/graphics/frame.png";
mDefaultProperties.mBackgroundCornerSize = glm::vec2 {16.0f, 16.0f};
mDefaultProperties.mBackgroundCenterColor = 0xAAAAEEFF;
mDefaultProperties.mBackgroundEdgeColor = 0xAAAAEEFF;
mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mImageColor = 0xFFFFFFFF;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize;
mSelectedProperties.mBackgroundCenterColor = 0xFFFFFFFF;
mSelectedProperties.mBackgroundEdgeColor = 0xFFFFFFFF;
mImage = std::make_shared<ImageComponent>();
mImage->setOrigin(0.5f, 0.5f);
mBackground.setOrigin(0.5f, 0.5f);
addChild(&mBackground);
addChild(&(*mImage));
mSelectedZoomPercent = 0;
mSelected = false;
mVisible = false;
setSelected(false, false);
setVisible(true);
}
void GridTileComponent::render(const glm::mat4& parentTrans)
{
glm::mat4 trans {getTransform() * parentTrans};
if (mVisible)
renderChildren(trans);
}
// Update all the tile properties to the new status (selected or default).
void GridTileComponent::update(int deltaTime)
{
GuiComponent::update(deltaTime);
calcCurrentProperties();
mBackground.setImagePath(mCurrentProperties.mBackgroundImage);
mImage->setColorShift(mCurrentProperties.mImageColor);
mBackground.setCenterColor(mCurrentProperties.mBackgroundCenterColor);
mBackground.setEdgeColor(mCurrentProperties.mBackgroundEdgeColor);
resize();
}
void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties* properties)
{
glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()};
if (elem->has("size"))
properties->mSize = elem->get<glm::vec2>("size") * screen;
if (elem->has("padding"))
properties->mPadding = elem->get<glm::vec2>("padding");
if (elem->has("imageColor"))
properties->mImageColor = elem->get<unsigned int>("imageColor");
if (elem->has("backgroundImage"))
properties->mBackgroundImage = elem->get<std::string>("backgroundImage");
if (elem->has("backgroundCornerSize"))
properties->mBackgroundCornerSize = elem->get<glm::vec2>("backgroundCornerSize");
if (elem->has("backgroundColor")) {
properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundColor");
properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundColor");
}
if (elem->has("backgroundCenterColor"))
properties->mBackgroundCenterColor = elem->get<unsigned int>("backgroundCenterColor");
if (elem->has("backgroundEdgeColor"))
properties->mBackgroundEdgeColor = elem->get<unsigned int>("backgroundEdgeColor");
}
void GridTileComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& /*element*/,
unsigned int /*properties*/)
{
// Apply theme to the default gridtile.
const ThemeData::ThemeElement* elem = theme->getElement(view, "default", "gridtile");
if (elem)
applyThemeToProperties(elem, &mDefaultProperties);
// Apply theme to the selected gridtile. Note that some of the default gridtile
// properties have influence on the selected gridtile properties.
// See THEMES.md for more information.
elem = theme->getElement(view, "selected", "gridtile");
mSelectedProperties.mSize = getSelectedTileSize();
mSelectedProperties.mPadding = mDefaultProperties.mPadding;
mSelectedProperties.mBackgroundImage = mDefaultProperties.mBackgroundImage;
mSelectedProperties.mBackgroundCornerSize = mDefaultProperties.mBackgroundCornerSize;
if (elem)
applyThemeToProperties(elem, &mSelectedProperties);
}
glm::vec2 GridTileComponent::getDefaultTileSize()
{
glm::vec2 screen {glm::vec2(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()))};
return screen * 0.22f;
}
glm::vec2 GridTileComponent::getSelectedTileSize() const
{
// Return the tile size.
return mDefaultProperties.mSize * 1.2f;
}
bool GridTileComponent::isSelected() const
{
// Return whether the tile is selected.
return mSelected;
}
void GridTileComponent::setImageOLD(const std::string& path)
{
mImage->setImage(path);
// Resize now to prevent flickering images when scrolling.
resize();
}
void GridTileComponent::setImageOLD(const std::shared_ptr<TextureResource>& texture)
{
mImage->setImage(texture);
// Resize now to prevent flickering images when scrolling.
resize();
}
void GridTileComponent::setSelected(bool selected,
bool allowAnimation,
glm::vec3* pPosition,
bool force)
{
if (mSelected == selected && !force)
return;
mSelected = selected;
if (selected) {
if (pPosition == nullptr || !allowAnimation) {
cancelAnimation(3);
this->setSelectedZoom(1);
mAnimPosition = {};
resize();
}
else {
mAnimPosition = glm::vec3 {pPosition->x, pPosition->y, pPosition->z};
auto func = [this](float t) {
t -= 1;
float pct = glm::mix(0.0f, 1.0f, t * t * t + 1.0f);
this->setSelectedZoom(pct);
};
cancelAnimation(3);
setAnimation(
new LambdaAnimation(func, 250), 0,
[this] {
this->setSelectedZoom(1);
mAnimPosition = {};
},
false, 3);
}
}
else {
if (!allowAnimation) {
cancelAnimation(3);
this->setSelectedZoom(0);
resize();
}
else {
this->setSelectedZoom(1);
auto func = [this](float t) {
t -= 1.0f;
float pct = glm::mix(0.0f, 1.0f, t * t * t + 1.0f);
this->setSelectedZoom(1.0f - pct);
};
cancelAnimation(3);
setAnimation(
new LambdaAnimation(func, 250), 0, [this] { this->setSelectedZoom(0); }, false, 3);
}
}
}
void GridTileComponent::setSelectedZoom(float percent)
{
if (mSelectedZoomPercent == percent)
return;
mSelectedZoomPercent = percent;
resize();
}
void GridTileComponent::setVisible(bool visible) { mVisible = visible; }
void GridTileComponent::resize()
{
calcCurrentProperties();
mImage->setMaxSize(mCurrentProperties.mSize - mCurrentProperties.mPadding * 2.0f);
mBackground.setCornerSize(mCurrentProperties.mBackgroundCornerSize);
mBackground.fitTo(mCurrentProperties.mSize - mBackground.getCornerSize() * 2.0f);
}
unsigned int mixColors(unsigned int first, unsigned int second, float percent)
{
unsigned char alpha0 = (first >> 24) & 0xFF;
unsigned char blue0 = (first >> 16) & 0xFF;
unsigned char green0 = (first >> 8) & 0xFF;
unsigned char red0 = first & 0xFF;
unsigned char alpha1 = (second >> 24) & 0xFF;
unsigned char blue1 = (second >> 16) & 0xFF;
unsigned char green1 = (second >> 8) & 0xFF;
unsigned char red1 = second & 0xFF;
unsigned char alpha = static_cast<unsigned char>(alpha0 * (1.0 - percent) + alpha1 * percent);
unsigned char blue = static_cast<unsigned char>(blue0 * (1.0 - percent) + blue1 * percent);
unsigned char green = static_cast<unsigned char>(green0 * (1.0 - percent) + green1 * percent);
unsigned char red = static_cast<unsigned char>(red0 * (1.0 - percent) + red1 * percent);
return (alpha << 24) | (blue << 16) | (green << 8) | red;
}
void GridTileComponent::calcCurrentProperties()
{
mCurrentProperties = mSelected ? mSelectedProperties : mDefaultProperties;
float zoomPercentInverse = 1.0f - mSelectedZoomPercent;
if (mSelectedZoomPercent != 0.0f && mSelectedZoomPercent != 1.0f) {
if (mDefaultProperties.mSize != mSelectedProperties.mSize)
mCurrentProperties.mSize = mDefaultProperties.mSize * zoomPercentInverse +
mSelectedProperties.mSize * mSelectedZoomPercent;
if (mDefaultProperties.mPadding != mSelectedProperties.mPadding)
mCurrentProperties.mPadding = mDefaultProperties.mPadding * zoomPercentInverse +
mSelectedProperties.mPadding * mSelectedZoomPercent;
if (mDefaultProperties.mImageColor != mSelectedProperties.mImageColor)
mCurrentProperties.mImageColor =
mixColors(mDefaultProperties.mImageColor, mSelectedProperties.mImageColor,
mSelectedZoomPercent);
if (mDefaultProperties.mBackgroundCornerSize != mSelectedProperties.mBackgroundCornerSize)
mCurrentProperties.mBackgroundCornerSize =
mDefaultProperties.mBackgroundCornerSize * zoomPercentInverse +
mSelectedProperties.mBackgroundCornerSize * mSelectedZoomPercent;
if (mDefaultProperties.mBackgroundCenterColor != mSelectedProperties.mBackgroundCenterColor)
mCurrentProperties.mBackgroundCenterColor =
mixColors(mDefaultProperties.mBackgroundCenterColor,
mSelectedProperties.mBackgroundCenterColor, mSelectedZoomPercent);
if (mDefaultProperties.mBackgroundEdgeColor != mSelectedProperties.mBackgroundEdgeColor)
mCurrentProperties.mBackgroundEdgeColor =
mixColors(mDefaultProperties.mBackgroundEdgeColor,
mSelectedProperties.mBackgroundEdgeColor, mSelectedZoomPercent);
}
}
glm::vec3 GridTileComponent::getBackgroundPosition()
{
return mBackground.getPosition() + mPosition;
}
std::shared_ptr<TextureResource> GridTileComponent::getTexture()
{
if (mImage != nullptr)
return mImage->getTexture();
return nullptr;
}
void GridTileComponent::forceSize(glm::vec2 size, float selectedZoom)
{
mDefaultProperties.mSize = size;
mSelectedProperties.mSize = size * selectedZoom;
}

View file

@ -1,79 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GridTileComponent.h
//
// X*Y tile grid, used indirectly by GridGamelistView via ImageGridComponent.
//
#ifndef ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
#define ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H
#include "ImageComponent.h"
#include "NinePatchComponent.h"
struct GridTileProperties {
glm::vec2 mSize;
glm::vec2 mPadding;
unsigned int mImageColor;
std::string mBackgroundImage;
glm::vec2 mBackgroundCornerSize;
unsigned int mBackgroundCenterColor;
unsigned int mBackgroundEdgeColor;
};
class GridTileComponent : public GuiComponent
{
public:
GridTileComponent();
void render(const glm::mat4& parentTrans) override;
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
// Made this a static function because the ImageGridComponent needs to know the default tile
// max size to calculate the grid dimension before it instantiates the GridTileComponents.
static glm::vec2 getDefaultTileSize();
glm::vec2 getSelectedTileSize() const;
bool isSelected() const;
void reset() { setImageOLD(""); }
void setImageOLD(const std::string& path);
void setImageOLD(const std::shared_ptr<TextureResource>& texture);
void setSelected(bool selected,
bool allowAnimation = true,
glm::vec3* pPosition = nullptr,
bool force = false);
void setVisible(bool visible);
void forceSize(glm::vec2 size, float selectedZoom);
glm::vec3 getBackgroundPosition();
void update(int deltaTime) override;
std::shared_ptr<TextureResource> getTexture();
private:
void resize();
void calcCurrentProperties();
void setSelectedZoom(float percent);
std::shared_ptr<ImageComponent> mImage;
NinePatchComponent mBackground;
GridTileProperties mDefaultProperties;
GridTileProperties mSelectedProperties;
GridTileProperties mCurrentProperties;
float mSelectedZoomPercent;
bool mSelected;
bool mVisible;
glm::vec3 mAnimPosition;
};
#endif // ES_CORE_COMPONENTS_GRID_TILE_COMPONENT_H

View file

@ -1,726 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// ImageGridComponent.cpp
//
// X*Y image grid, used by GridGamelistView.
//
#ifndef ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H
#define ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H
#include "GridTileComponent.h"
#include "Log.h"
#include "animations/LambdaAnimation.h"
#include "components/IList.h"
#include "resources/TextureResource.h"
#define EXTRAITEMS 2
enum ScrollDirection {
SCROLL_VERTICALLY,
SCROLL_HORIZONTALLY
};
enum ImageSource {
THUMBNAIL,
IMAGE,
MIXIMAGE,
SCREENSHOT,
COVER,
MARQUEE,
BOX3D
};
struct ImageGridData {
std::string texturePath;
};
template <typename T> class ImageGridComponent : public IList<ImageGridData, T>
{
protected:
using IList<ImageGridData, T>::mEntries;
using IList<ImageGridData, T>::mScrollTier;
using IList<ImageGridData, T>::listUpdate;
using IList<ImageGridData, T>::listInput;
using IList<ImageGridData, T>::listRenderTitleOverlay;
using IList<ImageGridData, T>::getTransform;
using IList<ImageGridData, T>::mSize;
using IList<ImageGridData, T>::mCursor;
using IList<ImageGridData, T>::mWindow;
using IList<ImageGridData, T>::IList;
public:
using IList<ImageGridData, T>::size;
using IList<ImageGridData, T>::isScrolling;
using IList<ImageGridData, T>::stopScrolling;
ImageGridComponent();
void add(const std::string& name, const std::string& imagePath, const T& obj);
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const glm::mat4& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
void onSizeChanged() override;
void setCursorChangedCallback(const std::function<void(CursorState state)>& func)
{
mCursorChangedCallback = func;
}
ImageSource getImageSource() { return mImageSource; }
protected:
void onCursorChanged(const CursorState& state) override;
private:
// Tiles.
void buildTiles();
void updateTiles(bool ascending = true,
bool allowAnimation = true,
bool updateSelectedState = true);
void updateTileAtPos(int tilePos, int imgPos, bool allowAnimation, bool updateSelectedState);
void calcGridDimension();
bool isScrollLoop();
bool isVertical() { return mScrollDirection == SCROLL_VERTICALLY; }
// Images and entries.
bool mEntriesDirty;
int mLastCursor;
std::string mDefaultGameTexture;
std::string mDefaultFolderTexture;
// Tiles.
bool mLastRowPartial;
glm::vec2 mAutoLayout;
float mAutoLayoutZoom;
glm::vec4 mPadding;
glm::vec2 mMargin;
glm::vec2 mTileSize;
glm::ivec2 mGridDimension;
std::shared_ptr<ThemeData> mTheme;
std::vector<std::shared_ptr<GridTileComponent>> mTiles;
int mStartPosition;
float mCamera;
float mCameraDirection;
// Miscellaneous.
bool mAnimate;
bool mCenterSelection;
bool mScrollLoop;
ScrollDirection mScrollDirection;
ImageSource mImageSource;
std::function<void(CursorState state)> mCursorChangedCallback;
};
template <typename T> ImageGridComponent<T>::ImageGridComponent()
{
glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()};
mCamera = 0.0f;
mCameraDirection = 1.0f;
mAutoLayout = glm::vec2 {};
mAutoLayoutZoom = 1.0f;
mStartPosition = 0;
mEntriesDirty = true;
mLastCursor = 0;
mDefaultGameTexture = ":/graphics/cartridge.svg";
mDefaultFolderTexture = ":/graphics/folder.svg";
mSize = screen * 0.80f;
mMargin = screen * 0.07f;
mPadding = {};
mTileSize = GridTileComponent::getDefaultTileSize();
mAnimate = true;
mCenterSelection = false;
mScrollLoop = false;
mScrollDirection = SCROLL_VERTICALLY;
mImageSource = THUMBNAIL;
}
template <typename T>
void ImageGridComponent<T>::add(const std::string& name, const std::string& imagePath, const T& obj)
{
typename IList<ImageGridData, T>::Entry entry;
entry.name = name;
entry.object = obj;
entry.data.texturePath = imagePath;
static_cast<IList<ImageGridData, T>*>(this)->add(entry);
mEntriesDirty = true;
}
template <typename T> bool ImageGridComponent<T>::input(InputConfig* config, Input input)
{
if (input.value != 0) {
int idx = isVertical() ? 0 : 1;
glm::ivec2 dir {};
if (config->isMappedLike("up", input))
dir[1 ^ idx] = -1;
else if (config->isMappedLike("down", input))
dir[1 ^ idx] = 1;
else if (config->isMappedLike("left", input))
dir[0 ^ idx] = -1;
else if (config->isMappedLike("right", input))
dir[0 ^ idx] = 1;
if (dir != glm::ivec2 {}) {
if (isVertical())
listInput(dir.x + dir.y * mGridDimension.x);
else
listInput(dir.x + dir.y * mGridDimension.y);
return true;
}
}
else {
if (config->isMappedLike("up", input) || config->isMappedLike("down", input) ||
config->isMappedLike("left", input) || config->isMappedLike("right", input)) {
stopScrolling();
}
}
return GuiComponent::input(config, input);
}
template <typename T> void ImageGridComponent<T>::update(int deltaTime)
{
GuiComponent::update(deltaTime);
listUpdate(deltaTime);
for (auto it = mTiles.begin(); it != mTiles.end(); ++it)
(*it)->update(deltaTime);
}
template <typename T> void ImageGridComponent<T>::render(const glm::mat4& parentTrans)
{
glm::mat4 trans {getTransform() * parentTrans};
glm::mat4 tileTrans {trans};
float offsetX {isVertical() ? 0.0f : mCamera * mCameraDirection * (mTileSize.x + mMargin.x)};
float offsetY {isVertical() ? mCamera * mCameraDirection * (mTileSize.y + mMargin.y) : 0.0f};
tileTrans = glm::translate(tileTrans, glm::vec3 {offsetX, offsetY, 0.0f});
if (mEntriesDirty) {
updateTiles();
mEntriesDirty = false;
}
// Create a clipRect to hide tiles used to buffer texture loading.
float scaleX = trans[0].x;
float scaleY = trans[1].y;
glm::ivec2 pos {static_cast<int>(std::round(trans[3].x)),
static_cast<int>(std::round(trans[3].y))};
glm::ivec2 size {static_cast<int>(std::round(mSize.x * scaleX)),
static_cast<int>(std::round(mSize.y * scaleY))};
Renderer::pushClipRect(pos, size);
// Render all the tiles but the selected one.
std::shared_ptr<GridTileComponent> selectedTile = nullptr;
for (auto it = mTiles.begin(); it != mTiles.end(); ++it) {
std::shared_ptr<GridTileComponent> tile = (*it);
// If it's the selected image, keep it for later, otherwise render it now.
if (tile->isSelected())
selectedTile = tile;
else
tile->render(tileTrans);
}
Renderer::popClipRect();
// Render the selected image on top of the others.
if (selectedTile != nullptr)
selectedTile->render(tileTrans);
listRenderTitleOverlay(trans);
GuiComponent::renderChildren(trans);
}
template <typename T>
void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
// Apply theme to GuiComponent but not the size property, which will be applied
// at the end of this function.
GuiComponent::applyTheme(theme, view, element, properties ^ ThemeFlags::SIZE);
// Keep the theme pointer to apply it on the tiles later on.
mTheme = theme;
glm::vec2 screen {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())};
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "imagegrid");
if (elem) {
if (elem->has("margin"))
mMargin = elem->get<glm::vec2>("margin") * screen;
if (elem->has("padding"))
mPadding = elem->get<glm::vec4>("padding") *
glm::vec4 {screen.x, screen.y, screen.x, screen.y};
if (elem->has("autoLayout"))
mAutoLayout = elem->get<glm::vec2>("autoLayout");
if (elem->has("autoLayoutSelectedZoom"))
mAutoLayoutZoom = elem->get<float>("autoLayoutSelectedZoom");
if (elem->has("imageSource")) {
auto direction = elem->get<std::string>("imageSource");
if (direction == "image")
mImageSource = IMAGE;
else if (direction == "miximage")
mImageSource = MIXIMAGE;
else if (direction == "screenshot")
mImageSource = SCREENSHOT;
else if (direction == "cover")
mImageSource = COVER;
else if (direction == "marquee")
mImageSource = MARQUEE;
else if (direction == "3dbox")
mImageSource = BOX3D;
else
mImageSource = THUMBNAIL;
}
else {
mImageSource = THUMBNAIL;
}
if (elem->has("scrollDirection"))
mScrollDirection =
(ScrollDirection)(elem->get<std::string>("scrollDirection") == "horizontal");
if (elem->has("centerSelection")) {
mCenterSelection = (elem->get<bool>("centerSelection"));
if (elem->has("scrollLoop"))
mScrollLoop = (elem->get<bool>("scrollLoop"));
}
if (elem->has("animate"))
mAnimate = (elem->get<bool>("animate"));
else
mAnimate = true;
if (elem->has("gameImage")) {
std::string path = elem->get<std::string>("gameImage");
if (!ResourceManager::getInstance().fileExists(path)) {
LOG(LogWarning) << "Could not replace default game image, check path: " << path;
}
else {
std::string oldDefaultGameTexture = mDefaultGameTexture;
mDefaultGameTexture = path;
// mEntries are already loaded at this point, so we need to update them with
// the new game image texture.
for (auto it = mEntries.begin(); it != mEntries.end(); ++it) {
if ((*it).data.texturePath == oldDefaultGameTexture)
(*it).data.texturePath = mDefaultGameTexture;
}
}
}
if (elem->has("folderImage")) {
std::string path = elem->get<std::string>("folderImage");
if (!ResourceManager::getInstance().fileExists(path)) {
LOG(LogWarning) << "Could not replace default folder image, check path: " << path;
}
else {
std::string oldDefaultFolderTexture = mDefaultFolderTexture;
mDefaultFolderTexture = path;
// mEntries are already loaded at this point, so we need to update them with
// the new folder image texture.
for (auto it = mEntries.begin(); it != mEntries.end(); ++it) {
if ((*it).data.texturePath == oldDefaultFolderTexture)
(*it).data.texturePath = mDefaultFolderTexture;
}
}
}
}
// We still need to manually get the grid tile size here, so we can recalculate the new
// grid dimension, and then (re)build the tiles.
elem = theme->getElement(view, "default", "gridtile");
mTileSize = elem && elem->has("size") ? elem->get<glm::vec2>("size") * screen :
GridTileComponent::getDefaultTileSize();
// Apply size property which will trigger a call to onSizeChanged() which will build the tiles.
GuiComponent::applyTheme(theme, view, element, ThemeFlags::SIZE);
// Trigger the call manually if the theme has no "imagegrid" element.
if (!elem)
buildTiles();
}
template <typename T> void ImageGridComponent<T>::onSizeChanged()
{
buildTiles();
updateTiles();
}
template <typename T> void ImageGridComponent<T>::onCursorChanged(const CursorState& state)
{
if (mLastCursor == mCursor) {
if (state == CURSOR_STOPPED && mCursorChangedCallback)
mCursorChangedCallback(state);
return;
}
bool direction = mCursor >= mLastCursor;
int diff = direction ? mCursor - mLastCursor : mLastCursor - mCursor;
if (isScrollLoop() && diff == static_cast<int>(mEntries.size()) - 1)
direction = !direction;
int oldStart = mStartPosition;
int dimScrollable = (isVertical() ? mGridDimension.y : mGridDimension.x) - 2 * EXTRAITEMS;
int dimOpposite = isVertical() ? mGridDimension.x : mGridDimension.y;
int centralCol = static_cast<int>((static_cast<float>(dimScrollable) - 0.5f) / 2.0f);
int maxCentralCol = dimScrollable / 2;
int oldCol = (mLastCursor / dimOpposite);
int col = (mCursor / dimOpposite);
int lastCol = static_cast<int>((mEntries.size() - 1) / dimOpposite);
int lastScroll = std::max(0, (lastCol + 1 - dimScrollable));
float startPos = 0;
float endPos = 1;
if (reinterpret_cast<GuiComponent*>(this)->isAnimationPlaying(2)) {
startPos = 0;
reinterpret_cast<GuiComponent*>(this)->cancelAnimation(2);
updateTiles(direction, false, false);
}
if (mAnimate) {
std::shared_ptr<GridTileComponent> oldTile = nullptr;
std::shared_ptr<GridTileComponent> newTile = nullptr;
int oldIdx = mLastCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (oldIdx >= 0 && oldIdx < static_cast<int>(mTiles.size()))
oldTile = mTiles[oldIdx];
int newIdx = mCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (isScrollLoop()) {
if (newIdx < 0)
newIdx += static_cast<int>(mEntries.size());
else if (newIdx >= static_cast<int>(mTiles.size()))
newIdx -= static_cast<int>(mEntries.size());
}
if (newIdx >= 0 && newIdx < static_cast<int>(mTiles.size()))
newTile = mTiles[newIdx];
for (auto it = mTiles.begin(); it != mTiles.end(); ++it) {
if ((*it)->isSelected() && *it != oldTile && *it != newTile) {
startPos = 0;
(*it)->setSelected(false, false, nullptr);
}
}
glm::vec3 oldPos {};
if (oldTile != nullptr && oldTile != newTile) {
oldPos = oldTile->getBackgroundPosition();
oldTile->setSelected(false, true, nullptr, true);
}
if (newTile != nullptr)
newTile->setSelected(true, true, oldPos == glm::vec3 {} ? nullptr : &oldPos, true);
}
int firstVisibleCol = mStartPosition / dimOpposite;
if ((col < centralCol || (col == 0 && col == centralCol)) && !mCenterSelection) {
mStartPosition = 0;
}
else if ((col - centralCol) > lastScroll && !mCenterSelection && !isScrollLoop()) {
mStartPosition = lastScroll * dimOpposite;
}
else if ((maxCentralCol != centralCol && col == firstVisibleCol + maxCentralCol) ||
col == firstVisibleCol + centralCol) {
if (col == firstVisibleCol + maxCentralCol)
mStartPosition = (col - maxCentralCol) * dimOpposite;
else
mStartPosition = (col - centralCol) * dimOpposite;
}
else {
if (oldCol == firstVisibleCol + maxCentralCol)
mStartPosition = (col - maxCentralCol) * dimOpposite;
else
mStartPosition = (col - centralCol) * dimOpposite;
}
auto lastCursor = mLastCursor;
mLastCursor = mCursor;
mCameraDirection = direction ? -1.0f : 1.0f;
mCamera = 0;
if (lastCursor < 0 || !mAnimate) {
updateTiles(direction, mAnimate && (lastCursor >= 0 || isScrollLoop()));
if (mCursorChangedCallback)
mCursorChangedCallback(state);
return;
}
if (mCursorChangedCallback)
mCursorChangedCallback(state);
bool moveCamera = (oldStart != mStartPosition);
auto func = [this, startPos, endPos, moveCamera](float t) {
if (!moveCamera)
return;
t -= 1.0f;
float pct = glm::mix(0.0f, 1.0f, t * t * t + 1.0f);
t = startPos * (1.0f - pct) + endPos * pct;
mCamera = t;
};
reinterpret_cast<GuiComponent*>(this)->setAnimation(
new LambdaAnimation(func, 250), 0,
[this, direction] {
mCamera = 0;
updateTiles(direction, false);
},
false, 2);
}
// Create and position tiles (mTiles).
template <typename T> void ImageGridComponent<T>::buildTiles()
{
mStartPosition = 0;
mTiles.clear();
calcGridDimension();
if (mCenterSelection) {
int dimScrollable = (isVertical() ? mGridDimension.y : mGridDimension.x) - 2 * EXTRAITEMS;
mStartPosition -= static_cast<int>(floorf(dimScrollable / 2.0f));
}
glm::vec2 tileDistance {mTileSize + mMargin};
if (mAutoLayout.x != 0.0f && mAutoLayout.y != 0.0f) {
auto x = (mSize.x - (mMargin.x * (mAutoLayout.x - 1.0f)) - mPadding.x - mPadding.z) /
static_cast<int>(mAutoLayout.x);
auto y = (mSize.y - (mMargin.y * (mAutoLayout.y - 1.0f)) - mPadding.y - mPadding.w) /
static_cast<int>(mAutoLayout.y);
mTileSize = glm::vec2 {x, y};
tileDistance = mTileSize + mMargin;
}
bool vert = isVertical();
glm::vec2 startPosition {mTileSize / 2.0f};
startPosition.x += mPadding.x;
startPosition.y += mPadding.y;
int X;
int Y;
// Layout tile size and position.
for (int y = 0; y < (vert ? mGridDimension.y : mGridDimension.x); ++y) {
for (int x = 0; x < (vert ? mGridDimension.x : mGridDimension.y); ++x) {
// Create tiles.
auto tile = std::make_shared<GridTileComponent>();
// In Vertical mode, tiles are ordered from left to right, then from top to bottom.
// In Horizontal mode, tiles are ordered from top to bottom, then from left to right.
X = vert ? x : y - EXTRAITEMS;
Y = vert ? y - EXTRAITEMS : x;
tile->setPosition(X * tileDistance.x + startPosition.x,
Y * tileDistance.y + startPosition.y);
tile->setOrigin(0.5f, 0.5f);
tile->setImage("");
if (mTheme)
tile->applyTheme(mTheme, "grid", "gridtile", ThemeFlags::ALL);
if (mAutoLayout.x != 0 && mAutoLayout.y != 0.0f)
tile->forceSize(mTileSize, mAutoLayoutZoom);
mTiles.push_back(tile);
}
}
}
template <typename T>
void ImageGridComponent<T>::updateTiles(bool ascending,
bool allowAnimation,
bool updateSelectedState)
{
if (!mTiles.size())
return;
// Stop updating the tiles at highest scroll speed.
if (mScrollTier == 3) {
for (int ti = 0; ti < static_cast<int>(mTiles.size()); ++ti) {
std::shared_ptr<GridTileComponent> tile = mTiles.at(ti);
tile->setSelected(false);
tile->setImage(mDefaultGameTexture);
tile->setVisible(false);
}
return;
}
// Temporarily store the previous textures so that they can't be unloaded.
std::vector<std::shared_ptr<TextureResource>> previousTextures;
for (int ti = 0; ti < static_cast<int>(mTiles.size()); ++ti) {
std::shared_ptr<GridTileComponent> tile = mTiles.at(ti);
previousTextures.push_back(tile->getTexture());
}
// If going down, update from top to bottom.
// If going up, update from bottom to top.
int scrollDirection = ascending ? 1 : -1;
int ti = ascending ? 0 : static_cast<int>(mTiles.size()) - 1;
int end = ascending ? static_cast<int>(mTiles.size()) : -1;
int img = mStartPosition + ti;
img -= EXTRAITEMS * (isVertical() ? mGridDimension.x : mGridDimension.y);
// Update the tiles.
while (ti != end) {
updateTileAtPos(ti, img, allowAnimation, updateSelectedState);
ti += scrollDirection;
img += scrollDirection;
}
if (updateSelectedState)
mLastCursor = mCursor;
mLastCursor = mCursor;
}
template <typename T>
void ImageGridComponent<T>::updateTileAtPos(int tilePos,
int imgPos,
bool allowAnimation,
bool updateSelectedState)
{
std::shared_ptr<GridTileComponent> tile = mTiles.at(tilePos);
if (isScrollLoop()) {
if (imgPos < 0)
imgPos += static_cast<int>(mEntries.size());
else if (imgPos >= size())
imgPos -= static_cast<int>(mEntries.size());
}
// If we have more tiles than we can display on screen, hide them.
// Same for tiles out of the buffer.
if (imgPos < 0 || imgPos >= size() || tilePos < 0 ||
tilePos >= static_cast<int>(mTiles.size())) {
if (updateSelectedState)
tile->setSelected(false, allowAnimation);
tile->reset();
tile->setVisible(false);
}
else {
tile->setVisible(true);
std::string imagePath = mEntries.at(imgPos).data.texturePath;
if (ResourceManager::getInstance().fileExists(imagePath))
tile->setImage(imagePath);
else if (mEntries.at(imgPos).object->getType() == 2)
tile->setImage(mDefaultFolderTexture);
else
tile->setImage(mDefaultGameTexture);
if (updateSelectedState) {
if (imgPos == mCursor && mCursor != mLastCursor) {
int dif = mCursor - tilePos;
int idx = mLastCursor - dif;
if (idx < 0 || idx >= static_cast<int>(mTiles.size()))
idx = 0;
glm::vec3 pos {mTiles.at(idx)->getBackgroundPosition()};
tile->setSelected(true, allowAnimation, &pos);
}
else {
tile->setSelected(imgPos == mCursor, allowAnimation);
}
}
}
}
// Calculate how many tiles of size mTileSize we can fit in a grid of size mSize using
// a margin size of mMargin.
template <typename T> void ImageGridComponent<T>::calcGridDimension()
{
// grid_size = columns * tile_size + (columns - 1) * margin
// <=> columns = (grid_size + margin) / (tile_size + margin)
glm::vec2 gridDimension {(mSize + mMargin) / (mTileSize + mMargin)};
if (mAutoLayout.x != 0.0f && mAutoLayout.y != 0.0f)
gridDimension = mAutoLayout;
mLastRowPartial = floorf(gridDimension.y) != gridDimension.y;
// Ceil y dim so we can display partial last row.
mGridDimension = glm::ivec2 {static_cast<const int>(gridDimension.x),
static_cast<const int>(ceilf(gridDimension.y))};
// Grid dimension validation.
if (mGridDimension.x < 1) {
LOG(LogError) << "Theme defined grid X dimension below 1";
}
if (mGridDimension.y < 1) {
LOG(LogError) << "Theme defined grid Y dimension below 1";
}
// Add extra tiles to both sides.
if (isVertical())
mGridDimension.y += 2 * EXTRAITEMS;
else
mGridDimension.x += 2 * EXTRAITEMS;
}
template <typename T> bool ImageGridComponent<T>::isScrollLoop()
{
if (!mScrollLoop)
return false;
if (isVertical())
return (mGridDimension.x * (mGridDimension.y - 2 * EXTRAITEMS)) <=
static_cast<int>(mEntries.size());
return (mGridDimension.y * (mGridDimension.x - 2 * EXTRAITEMS)) <=
static_cast<int>(mEntries.size());
}
#endif // ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H