mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-02-16 20:15:38 +00:00
Removed a lot of deprecated theme engine code from the legacy engine.
This commit is contained in:
parent
74d3e1f063
commit
596bc5e8af
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
Loading…
Reference in a new issue