mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 06:05: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