mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-29 09:35:39 +00:00
Added the preliminary GamelistBase and GamelistView classes.
This commit is contained in:
parent
50db59a6f6
commit
ec0a7ad2f1
|
@ -55,6 +55,8 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGamelistView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGamelistView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGamelistView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GamelistBase.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GamelistView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h
|
||||
)
|
||||
|
@ -105,6 +107,8 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGamelistView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGamelistView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGamelistView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GamelistBase.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GamelistView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
|
||||
)
|
||||
|
|
795
es-app/src/views/GamelistBase.cpp
Normal file
795
es-app/src/views/GamelistBase.cpp
Normal file
|
@ -0,0 +1,795 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistBase.cpp
|
||||
//
|
||||
// Gamelist base class with utility functions and other low-level logic.
|
||||
//
|
||||
|
||||
#include "views/GamelistBase.h"
|
||||
|
||||
#include "CollectionSystemsManager.h"
|
||||
#include "FileFilterIndex.h"
|
||||
#include "UIModeController.h"
|
||||
#include "guis/GuiGamelistOptions.h"
|
||||
#include "views/ViewController.h"
|
||||
|
||||
GamelistBase::GamelistBase(Window* window, FileData* root)
|
||||
: GuiComponent {window}
|
||||
, mRoot {root}
|
||||
, mList {window}
|
||||
, mRandomGame {nullptr}
|
||||
, mLastUpdated(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
GamelistBase::~GamelistBase()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void GamelistBase::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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GamelistBase::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) {
|
||||
// TEMPORARY
|
||||
// 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.
|
||||
// TEMPORARY
|
||||
// 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()) {
|
||||
// TEMPORARY
|
||||
// ViewController::getInstance()
|
||||
// ->getGamelistView(entryToUpdate->getSystem())
|
||||
// ->setCursor(ViewController::getInstance()
|
||||
// ->getGamelistView(entryToUpdate->getSystem())
|
||||
// ->getFirstGameEntry());
|
||||
}
|
||||
else if (removedLastFavorite &&
|
||||
!entryToUpdate->getSystem()->isCustomCollection()) {
|
||||
setCursor(getFirstEntry());
|
||||
// view->setCursor(view->getFirstEntry());
|
||||
}
|
||||
else if (selectLastEntry) {
|
||||
setCursor(getLastEntry());
|
||||
// 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) {
|
||||
// TEMPORARY
|
||||
// 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);
|
||||
// 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";
|
||||
// TEMPORARY
|
||||
// ViewController::getInstance()->reloadGamelistView(this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return GuiComponent::input(config, input);
|
||||
}
|
||||
|
||||
void GamelistBase::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();
|
||||
|
||||
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 GamelistBase::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 GamelistBase::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);
|
||||
}
|
||||
|
||||
void GamelistBase::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 GamelistBase::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<unsigned 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(nullptr);
|
||||
|
||||
// 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 GamelistBase::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);
|
||||
}
|
||||
}
|
101
es-app/src/views/GamelistBase.h
Normal file
101
es-app/src/views/GamelistBase.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistBase.h
|
||||
//
|
||||
// Gamelist base class with utility functions and other low-level logic.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_VIEWS_GAMELIST_BASE_H
|
||||
#define ES_APP_VIEWS_GAMELIST_BASE_H
|
||||
|
||||
#include "FileData.h"
|
||||
#include "GuiComponent.h"
|
||||
#include "SystemData.h"
|
||||
#include "ThemeData.h"
|
||||
#include "Window.h"
|
||||
#include "components/BadgeComponent.h"
|
||||
#include "components/DateTimeComponent.h"
|
||||
#include "components/RatingComponent.h"
|
||||
#include "components/ScrollableContainer.h"
|
||||
#include "components/TextComponent.h"
|
||||
#include "components/TextListComponent.h"
|
||||
|
||||
#include <stack>
|
||||
|
||||
class GamelistBase : public GuiComponent
|
||||
{
|
||||
public:
|
||||
FileData* getCursor() { return mList.getSelected(); }
|
||||
void setCursor(FileData*);
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
|
||||
FileData* getNextEntry() { return mList.getNext(); }
|
||||
FileData* getPreviousEntry() { return mList.getPrevious(); }
|
||||
FileData* getFirstEntry() { return mList.getFirst(); }
|
||||
FileData* getLastEntry() { return mList.getLast(); }
|
||||
FileData* getFirstGameEntry() { return mFirstGameEntry; }
|
||||
|
||||
protected:
|
||||
GamelistBase(Window* window, FileData* root);
|
||||
~GamelistBase();
|
||||
|
||||
// Called when a FileData* is added, has its metadata changed, or is removed.
|
||||
virtual void onFileChanged(FileData* file, bool reloadGamelist) = 0;
|
||||
|
||||
void populateList(const std::vector<FileData*>& files, FileData* firstEntry);
|
||||
void addPlaceholder(FileData*);
|
||||
|
||||
void generateFirstLetterIndex(const std::vector<FileData*>& files);
|
||||
void generateGamelistInfo(FileData* cursor, FileData* firstEntry);
|
||||
|
||||
void remove(FileData* game, bool deleteFile);
|
||||
void removeMedia(FileData* game);
|
||||
|
||||
virtual void launch(FileData* game) = 0;
|
||||
|
||||
bool isListScrolling() override { return mList.isScrolling(); }
|
||||
void stopListScrolling() override { mList.stopScrolling(); }
|
||||
|
||||
const std::vector<std::string>& getFirstLetterIndex() { return mFirstLetterIndex; }
|
||||
std::string getQuickSystemSelectRightButton() { return "right"; }
|
||||
std::string getQuickSystemSelectLeftButton() { return "left"; }
|
||||
|
||||
// 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)
|
||||
{
|
||||
cursorHistory = mCursorStackHistory;
|
||||
}
|
||||
void populateCursorHistory(std::vector<FileData*>& cursorHistory)
|
||||
{
|
||||
mCursorStackHistory = cursorHistory;
|
||||
}
|
||||
|
||||
FileData* mRoot;
|
||||
TextListComponent<FileData*> mList;
|
||||
|
||||
// Points to the first game in the list, i.e. the first entry which is of the type "GAME".
|
||||
FileData* mFirstGameEntry;
|
||||
|
||||
// This game is randomly selected in the grouped custom collections view.
|
||||
FileData* mRandomGame;
|
||||
FileData* mLastUpdated;
|
||||
|
||||
std::stack<FileData*> mCursorStack;
|
||||
std::vector<FileData*> mCursorStackHistory;
|
||||
|
||||
std::vector<std::string> mFirstLetterIndex;
|
||||
|
||||
unsigned int mGameCount;
|
||||
unsigned int mFavoritesGameCount;
|
||||
unsigned int mFilteredGameCount;
|
||||
unsigned int mFilteredGameCountAll;
|
||||
bool mIsFiltered;
|
||||
bool mIsFolder;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif // ES_APP_VIEWS_GAMELIST_BASE_H
|
558
es-app/src/views/GamelistView.cpp
Normal file
558
es-app/src/views/GamelistView.cpp
Normal file
|
@ -0,0 +1,558 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistView.cpp
|
||||
//
|
||||
// Main gamelist logic.
|
||||
//
|
||||
|
||||
#include "views/GamelistView.h"
|
||||
|
||||
#include "CollectionSystemsManager.h"
|
||||
#include "UIModeController.h"
|
||||
#include "animations/LambdaAnimation.h"
|
||||
|
||||
#define FADE_IN_START_OPACITY 0.5f
|
||||
#define FADE_IN_TIME 650
|
||||
|
||||
GamelistView::GamelistView(Window* window, FileData* root)
|
||||
: GamelistBase {window, root}
|
||||
, mHeaderText {window}
|
||||
, mHeaderImage {window}
|
||||
, mBackground {window}
|
||||
, 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}
|
||||
{
|
||||
mHeaderText.setText(mRoot->getSystem()->getFullName());
|
||||
}
|
||||
|
||||
GamelistView::~GamelistView()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void GamelistView::onFileChanged(FileData* file, bool reloadGamelist)
|
||||
{
|
||||
if (reloadGamelist) {
|
||||
// Might switch to a detailed view.
|
||||
// TEMPORARY.
|
||||
// ViewController::getInstance()->reloadGamelistView(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void GamelistView::onShow()
|
||||
{
|
||||
// Reset any Lottie animations.
|
||||
for (auto extra : mThemeExtras)
|
||||
extra->resetFileAnimation();
|
||||
|
||||
mLastUpdated = nullptr;
|
||||
GuiComponent::onShow();
|
||||
updateInfoPanel();
|
||||
}
|
||||
|
||||
void GamelistView::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);
|
||||
}
|
||||
|
||||
mList.applyTheme(theme, getName(), "gamelist", ALL);
|
||||
|
||||
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 GamelistView::update(int deltaTime)
|
||||
{
|
||||
// TEMPORARY
|
||||
// BasicGamelistView::update(deltaTime);
|
||||
mImage.update(deltaTime);
|
||||
|
||||
if (ViewController::getInstance()->getGameLaunchTriggered() && mImage.isAnimationPlaying(0))
|
||||
mImage.finishAnimation(0);
|
||||
}
|
||||
|
||||
void GamelistView::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();
|
||||
}
|
||||
|
||||
HelpStyle GamelistView::getHelpStyle()
|
||||
{
|
||||
HelpStyle style;
|
||||
style.applyTheme(mTheme, getName());
|
||||
return style;
|
||||
}
|
||||
|
||||
std::vector<HelpPrompt> GamelistView::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;
|
||||
}
|
||||
|
||||
void GamelistView::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 GamelistView::initMDLabels()
|
||||
{
|
||||
std::vector<TextComponent*> components {getMDLabels()};
|
||||
|
||||
const unsigned int colCount {2};
|
||||
const unsigned int rowCount {static_cast<unsigned 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 GamelistView::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);
|
||||
}
|
||||
|
||||
std::vector<TextComponent*> GamelistView::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*> GamelistView::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;
|
||||
}
|
91
es-app/src/views/GamelistView.h
Normal file
91
es-app/src/views/GamelistView.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistView.h
|
||||
//
|
||||
// Main gamelist logic.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_VIEWS_GAMELIST_VIEW_H
|
||||
#define ES_APP_VIEWS_GAMELIST_VIEW_H
|
||||
|
||||
#include "views/GamelistBase.h"
|
||||
|
||||
#include "renderers/Renderer.h"
|
||||
#include "views/ViewController.h"
|
||||
|
||||
class GamelistView : public GamelistBase
|
||||
{
|
||||
public:
|
||||
GamelistView(Window* window, FileData* root);
|
||||
~GamelistView();
|
||||
|
||||
// Called when a FileData* is added, has its metadata changed, or is removed.
|
||||
void onFileChanged(FileData* file, bool reloadGamelist) override;
|
||||
void onShow() override;
|
||||
|
||||
void preloadGamelist() { updateInfoPanel(); }
|
||||
void launch(FileData* game) override { ViewController::getInstance()->triggerGameLaunch(game); }
|
||||
|
||||
std::string getName() const { return "DEPRECATED FUNCTION"; }
|
||||
|
||||
const std::shared_ptr<ThemeData> getTheme() const { return mTheme; }
|
||||
void setTheme(const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
mTheme = theme;
|
||||
onThemeChanged(theme);
|
||||
}
|
||||
void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
|
||||
|
||||
void update(int deltaTime) override;
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
HelpStyle getHelpStyle() override;
|
||||
std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
void updateInfoPanel();
|
||||
|
||||
void initMDLabels();
|
||||
void initMDValues();
|
||||
|
||||
std::vector<TextComponent*> getMDLabels();
|
||||
std::vector<GuiComponent*> getMDValues();
|
||||
|
||||
std::shared_ptr<ThemeData> mTheme;
|
||||
std::vector<GuiComponent*> mThemeExtras;
|
||||
|
||||
TextComponent mHeaderText;
|
||||
ImageComponent mHeaderImage;
|
||||
ImageComponent mBackground;
|
||||
|
||||
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;
|
||||
|
||||
ScrollableContainer mDescContainer;
|
||||
TextComponent mDescription;
|
||||
TextComponent mGamelistInfo;
|
||||
};
|
||||
|
||||
#endif // ES_APP_VIEWS_GAMELIST_VIEW_H
|
Loading…
Reference in a new issue