Added support for launching game files inside folders without having to enter the folder.

This commit is contained in:
Leon Styhre 2022-04-11 23:28:18 +02:00
parent 9c485aeef8
commit 45af7441e9
13 changed files with 282 additions and 42 deletions

View file

@ -23,6 +23,7 @@
#include "utils/PlatformUtil.h" #include "utils/PlatformUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "utils/TimeUtil.h" #include "utils/TimeUtil.h"
#include "views/GamelistView.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include <assert.h> #include <assert.h>
@ -1233,12 +1234,19 @@ void FileData::launchGame()
// Update number of times the game has been launched. // Update number of times the game has been launched.
FileData* gameToUpdate = getSourceFileData(); FileData* gameToUpdate = getSourceFileData();
int timesPlayed = gameToUpdate->metadata.getInt("playcount") + 1; int timesPlayed {gameToUpdate->metadata.getInt("playcount") + 1};
gameToUpdate->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed))); gameToUpdate->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed)));
// Update last played time. // Update last played time.
gameToUpdate->metadata.set("lastplayed", Utils::Time::DateTime(Utils::Time::now())); gameToUpdate->metadata.set("lastplayed", Utils::Time::DateTime(Utils::Time::now()));
// If the cursor is a folder, the "launch file" functionality must have been used, so set
// the lastplayed timestamp for this folder to the same as the launched game.
FileData* cursor {
ViewController::getInstance()->getGamelistView(gameToUpdate->getSystem())->getCursor()};
if (cursor->getType() == FOLDER)
cursor->metadata.set("lastplayed", gameToUpdate->metadata.get("lastplayed"));
// If the parent is a folder and it's not the root of the system, then update its lastplayed // If the parent is a folder and it's not the root of the system, then update its lastplayed
// timestamp to the same time as the game that was just launched. // timestamp to the same time as the game that was just launched.
if (gameToUpdate->getParent()->getType() == FOLDER && if (gameToUpdate->getParent()->getType() == FOLDER &&

View file

@ -20,7 +20,7 @@ namespace
// The statistic entries must be placed at the bottom or otherwise there will be problems with // The statistic entries must be placed at the bottom or otherwise there will be problems with
// saving the values in GuiMetaDataEd. // saving the values in GuiMetaDataEd.
MetaDataDecl gameDecls[] { MetaDataDecl gameDecls[] {
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape // Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "name", "enter name", true},
{"sortname", MD_STRING, "", false, "sortname", "enter sortname", false}, {"sortname", MD_STRING, "", false, "sortname", "enter sortname", false},
{"collectionsortname", MD_STRING, "", false, "custom collections sortname", "enter collections sortname", false}, {"collectionsortname", MD_STRING, "", false, "custom collections sortname", "enter collections sortname", false},
@ -46,6 +46,7 @@ namespace
}; };
MetaDataDecl folderDecls[] { MetaDataDecl folderDecls[] {
// Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "name", "enter name", true},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, {"rating", MD_RATING, "0", false, "rating", "enter rating", true},
@ -62,6 +63,7 @@ namespace
{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false},
{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false},
{"controller", MD_CONTROLLER, "", false, "controller", "select controller", true}, {"controller", MD_CONTROLLER, "", false, "controller", "select controller", true},
{"launchfile", MD_LAUNCH_FILE, "", false, "launch file", "select launch file", false},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false}
}; };
// clang-format on // clang-format on

View file

@ -34,6 +34,7 @@ enum MetaDataType {
MD_MULTILINE_STRING, MD_MULTILINE_STRING,
MD_CONTROLLER, MD_CONTROLLER,
MD_ALT_EMULATOR, MD_ALT_EMULATOR,
MD_LAUNCH_FILE,
MD_PATH, MD_PATH,
MD_RATING, MD_RATING,
MD_DATE, MD_DATE,

View file

@ -212,12 +212,28 @@ void Screensaver::nextGame()
void Screensaver::launchGame() void Screensaver::launchGame()
{ {
if (mCurrentGame != nullptr) { if (mCurrentGame != nullptr) {
// If the game is inside a folder where a "launch file" entry is present, then jump to
// that folder instead of to the actual game file. Also check the complete hierarchy in
// case launch file entries are set on multiple levels of the folder structure.
FileData* entry {mCurrentGame};
FileData* selectGame {mCurrentGame};
FileData* launchFolder {nullptr};
while (entry != nullptr) {
entry = entry->getParent();
if (entry != nullptr && entry->metadata.get("launchfile") != "")
launchFolder = entry;
}
if (launchFolder != nullptr)
selectGame = launchFolder;
// Launching game // Launching game
ViewController::getInstance()->triggerGameLaunch(mCurrentGame); ViewController::getInstance()->triggerGameLaunch(mCurrentGame);
ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem()); ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem());
GamelistView* view { GamelistView* view {
ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get()}; ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get()};
view->setCursor(mCurrentGame); view->setCursor(selectGame);
view->stopListScrolling(); view->stopListScrolling();
ViewController::getInstance()->cancelViewTransitions(); ViewController::getInstance()->cancelViewTransitions();
ViewController::getInstance()->pauseViewVideos(); ViewController::getInstance()->pauseViewVideos();
@ -227,6 +243,18 @@ void Screensaver::launchGame()
void Screensaver::goToGame() void Screensaver::goToGame()
{ {
if (mCurrentGame != nullptr) { if (mCurrentGame != nullptr) {
FileData* entry {mCurrentGame};
FileData* launchFolder {nullptr};
while (entry != nullptr) {
entry = entry->getParent();
if (entry != nullptr && entry->metadata.get("launchfile") != "")
launchFolder = entry;
}
if (launchFolder != nullptr)
mCurrentGame = launchFolder;
// Go to the game in the gamelist view, but don't launch it. // Go to the game in the gamelist view, but don't launch it.
ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem()); ViewController::getInstance()->goToGamelist(mCurrentGame->getSystem());
GamelistView* view { GamelistView* view {

View file

@ -10,6 +10,11 @@
// metadata edit interface is covered by GuiMetaDataEd. // metadata edit interface is covered by GuiMetaDataEd.
// //
#if defined(_WIN64)
// Why this is needed here is anyone's guess but without it the compilation fails.
#include <winsock2.h>
#endif
#include "GuiGamelistOptions.h" #include "GuiGamelistOptions.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
@ -33,6 +38,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
, mCancelled {false} , mCancelled {false}
, mIsCustomCollection {false} , mIsCustomCollection {false}
, mIsCustomCollectionGroup {false} , mIsCustomCollectionGroup {false}
, mLaunchFileOverride {false}
, mCustomCollectionSystem {nullptr} , mCustomCollectionSystem {nullptr}
{ {
addChild(&mMenu); addChild(&mMenu);
@ -228,6 +234,19 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
} }
} }
if (file->getType() == FOLDER && file->metadata.get("launchfile") != "") {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>("ENTER FOLDER (OVERRIDE LAUNCH FILE)",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.makeAcceptInputHandler([this, file] {
mLaunchFileOverride = true;
getGamelist()->enterDirectory(file);
delete this;
});
mMenu.addRow(row);
}
// Buttons. The logic to apply or cancel settings are handled by the destructor. // Buttons. The logic to apply or cancel settings are handled by the destructor.
if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) || if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) ||
system->getName() == "recent") { system->getName() == "recent") {
@ -308,7 +327,8 @@ GuiGamelistOptions::~GuiGamelistOptions()
} }
} }
if (mSystem->getRootFolder()->getChildren().size() != 0 && mSystem->getName() != "recent") if (mSystem->getRootFolder()->getChildren().size() != 0 && mSystem->getName() != "recent" &&
!mLaunchFileOverride)
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }

View file

@ -59,6 +59,7 @@ private:
bool mCancelled; bool mCancelled;
bool mIsCustomCollection; bool mIsCustomCollection;
bool mIsCustomCollectionGroup; bool mIsCustomCollectionGroup;
bool mLaunchFileOverride;
SystemData* mCustomCollectionSystem; SystemData* mCustomCollectionSystem;
std::vector<std::string> mFirstLetterIndex; std::vector<std::string> mFirstLetterIndex;
std::string mCurrentFirstCharacter; std::string mCurrentFirstCharacter;

View file

@ -52,6 +52,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
, mMediaFilesUpdated {false} , mMediaFilesUpdated {false}
, mSavedMediaAndAborted {false} , mSavedMediaAndAborted {false}
, mInvalidEmulatorEntry {false} , mInvalidEmulatorEntry {false}
, mInvalidLaunchFileEntry {false}
{ {
if (ViewController::getInstance()->getState().getSystem()->isCustomCollection() || if (ViewController::getInstance()->getState().getSystem()->isCustomCollection() ||
ViewController::getInstance()->getState().getSystem()->getThemeFolder() == ViewController::getInstance()->getState().getSystem()->getThemeFolder() ==
@ -70,9 +71,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2}); mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2});
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
std::string folderPath = std::string folderPath {
Utils::String::replace(Utils::FileSystem::getParent(scraperParams.game->getPath()), Utils::String::replace(Utils::FileSystem::getParent(scraperParams.game->getPath()),
scraperParams.system->getSystemEnvData()->mStartPath, ""); scraperParams.system->getSystemEnvData()->mStartPath, "")};
if (folderPath.size() >= 2) { if (folderPath.size() >= 2) {
folderPath.erase(0, 1); folderPath.erase(0, 1);
@ -112,8 +113,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
// Populate list. // Populate list.
for (auto it = mdd.cbegin(); it != mdd.cend(); ++it) { for (auto it = mdd.cbegin(); it != mdd.cend(); ++it) {
std::shared_ptr<GuiComponent> ed; std::shared_ptr<GuiComponent> ed;
std::string currentKey = it->key; std::string currentKey {it->key};
std::string originalValue = mMetaData->get(it->key); std::string originalValue {mMetaData->get(it->key)};
std::string gamePath; std::string gamePath;
// Only display the custom collections sortname entry if we're editing the game // Only display the custom collections sortname entry if we're editing the game
@ -162,7 +163,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed = std::make_shared<RatingComponent>(true); ed = std::make_shared<RatingComponent>(true);
ed->setChangedColor(ICONCOLOR_USERMARKED); ed->setChangedColor(ICONCOLOR_USERMARKED);
const float height = lbl->getSize().y * 0.71f; const float height {lbl->getSize().y * 0.71f};
ed->setSize(0.0f, height); ed->setSize(0.0f, height);
row.addElement(ed, false, true); row.addElement(ed, false, true);
@ -200,7 +201,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()}); bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()});
row.addElement(bracket, false); row.addElement(bracket, false);
const std::string title = it->displayPrompt; const std::string title {it->displayPrompt};
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
auto updateVal = [ed, originalValue](const std::string& newVal) { auto updateVal = [ed, originalValue](const std::string& newVal) {
@ -212,15 +213,15 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
}; };
row.makeAcceptInputHandler([this, title, ed, updateVal] { row.makeAcceptInputHandler([this, title, ed, updateVal] {
GuiSettings* s = new GuiSettings(title); GuiSettings* s {new GuiSettings(title)};
for (auto controller : mControllerBadges) { for (auto controller : mControllerBadges) {
std::string selectedLabel = ed->getValue(); std::string selectedLabel {ed->getValue()};
std::string label; std::string label;
ComponentListRow row; ComponentListRow row;
std::shared_ptr<TextComponent> labelText = std::make_shared<TextComponent>( std::shared_ptr<TextComponent> labelText {std::make_shared<TextComponent>(
label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF)};
labelText->setSelectable(true); labelText->setSelectable(true);
labelText->setValue(controller.displayName); labelText->setValue(controller.displayName);
@ -243,9 +244,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
// If a value is set, then display "Clear entry" as the last entry. // If a value is set, then display "Clear entry" as the last entry.
if (ed->getValue() != "") { if (ed->getValue() != "") {
ComponentListRow row; ComponentListRow row;
std::shared_ptr<TextComponent> clearText = std::make_shared<TextComponent>( std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>(
ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF); Font::get(FONT_SIZE_MEDIUM), 0x777777FF)};
clearText->setSelectable(true); clearText->setSelectable(true);
row.addElement(clearText, true); row.addElement(clearText, true);
row.makeAcceptInputHandler([s, ed] { row.makeAcceptInputHandler([s, ed] {
@ -282,7 +283,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()}); bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()});
row.addElement(bracket, false); row.addElement(bracket, false);
const std::string title = it->displayPrompt; const std::string title {it->displayPrompt};
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
auto updateVal = [this, ed, originalValue](const std::string& newVal) { auto updateVal = [this, ed, originalValue](const std::string& newVal) {
@ -313,10 +314,10 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) { scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) {
row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal, row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal,
originalValue] { originalValue] {
GuiSettings* s = nullptr; GuiSettings* s {nullptr};
bool singleEntry = bool singleEntry {
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1; scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1};
if (mInvalidEmulatorEntry && singleEntry) if (mInvalidEmulatorEntry && singleEntry)
s = new GuiSettings("CLEAR INVALID ENTRY"); s = new GuiSettings("CLEAR INVALID ENTRY");
@ -326,8 +327,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
if (!mInvalidEmulatorEntry && ed->getValue() == "" && singleEntry) if (!mInvalidEmulatorEntry && ed->getValue() == "" && singleEntry)
return; return;
std::vector<std::pair<std::string, std::string>> launchCommands = std::vector<std::pair<std::string, std::string>> launchCommands {
scraperParams.system->getSystemEnvData()->mLaunchCommands; scraperParams.system->getSystemEnvData()->mLaunchCommands};
if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry) if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry)
launchCommands.push_back(std::make_pair( launchCommands.push_back(std::make_pair(
@ -342,7 +343,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ViewController::EXCLAMATION_CHAR + " " + originalValue) ViewController::EXCLAMATION_CHAR + " " + originalValue)
continue; continue;
std::string selectedLabel = ed->getValue(); std::string selectedLabel {ed->getValue()};
std::string label; std::string label;
ComponentListRow row; ComponentListRow row;
@ -351,9 +352,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
else else
label = entry.second; label = entry.second;
std::shared_ptr<TextComponent> labelText = std::shared_ptr<TextComponent> labelText {
std::make_shared<TextComponent>(label, Font::get(FONT_SIZE_MEDIUM), std::make_shared<TextComponent>(label, Font::get(FONT_SIZE_MEDIUM),
0x777777FF); 0x777777FF)};
labelText->setSelectable(true); labelText->setSelectable(true);
if (scraperParams.system->getAlternativeEmulator() == "" && if (scraperParams.system->getAlternativeEmulator() == "" &&
@ -404,6 +405,110 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
} }
break; break;
} }
case MD_LAUNCH_FILE: {
ed = std::make_shared<TextComponent>(
"", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>();
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>();
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()});
row.addElement(bracket, false);
const std::string title {it->displayPrompt};
std::vector<FileData*> children;
if (originalValue != "")
mInvalidLaunchFileEntry = true;
for (auto child : mScraperParams.game->getChildrenRecursive()) {
if (child->getType() == GAME && child->getCountAsGame() &&
!child->getHidden()) {
children.emplace_back(child);
std::string filePath {child->getPath()};
std::string systemPath {child->getSystem()->getRootFolder()->getPath() +
"/" + mScraperParams.game->getFileName() + "/"};
if (Utils::String::replace(filePath, systemPath, "") == originalValue)
mInvalidLaunchFileEntry = false;
}
}
// OK callback (apply new value to ed).
auto updateVal = [this, ed, originalValue](const std::string& newVal) {
mInvalidLaunchFileEntry = false;
ed->setValue(newVal);
if (newVal == originalValue)
ed->setColor(DEFAULT_TEXTCOLOR);
else
ed->setColor(TEXTCOLOR_USERMARKED);
};
row.makeAcceptInputHandler([this, children, title, ed, updateVal] {
GuiSettings* s {new GuiSettings(title)};
for (auto child : children) {
std::string selectedLabel {ed->getValue()};
std::string label;
ComponentListRow row;
std::string filePath {child->getPath()};
std::string systemPath {child->getSystem()->getRootFolder()->getPath() +
"/" + mScraperParams.game->getFileName() + "/"};
filePath = Utils::String::replace(filePath, systemPath, "");
std::shared_ptr<TextComponent> labelText {std::make_shared<TextComponent>(
label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF)};
labelText->setSelectable(true);
labelText->setValue(filePath);
label = filePath;
row.addElement(labelText, true);
row.makeAcceptInputHandler([s, updateVal, filePath] {
updateVal(filePath);
delete s;
});
// Select the row that corresponds to the selected label.
if (selectedLabel == label)
s->addRow(row, true);
else
s->addRow(row, false);
}
// If a value is set, then display "Clear entry" as the last entry.
if (ed->getValue() != "") {
ComponentListRow row;
std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>(
ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF)};
clearText->setSelectable(true);
row.addElement(clearText, true);
row.makeAcceptInputHandler([this, s, ed] {
mInvalidLaunchFileEntry = false;
ed->setValue("");
delete s;
});
s->addRow(row, false);
}
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float maxWidthModifier {glm::clamp(0.64f * aspectValue, 0.42f, 0.92f)};
float maxWidth {Renderer::getScreenWidth() * maxWidthModifier};
s->setMenuSize(glm::vec2 {maxWidth, s->getMenuSize().y});
s->setMenuPosition(
glm::vec3 {(s->getSize().x - maxWidth) / 2.0f, mPosition.y, mPosition.z});
mWindow->pushGui(s);
});
break;
}
case MD_MULTILINE_STRING: case MD_MULTILINE_STRING:
default: { default: {
// MD_STRING. // MD_STRING.
@ -420,8 +525,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()}); bracket->setResize(glm::vec2 {0.0f, lbl->getFont()->getLetterHeight()});
row.addElement(bracket, false); row.addElement(bracket, false);
bool multiLine = it->type == MD_MULTILINE_STRING; bool multiLine {it->type == MD_MULTILINE_STRING};
const std::string title = it->displayPrompt; const std::string title {it->displayPrompt};
gamePath = Utils::FileSystem::getStem(mScraperParams.game->getPath()); gamePath = Utils::FileSystem::getStem(mScraperParams.game->getPath());
@ -488,11 +593,14 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
assert(ed); assert(ed);
mList->addRow(row); mList->addRow(row);
if (it->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) { if (it->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry) {
ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue);
}
else if (it->type == MD_LAUNCH_FILE && mInvalidLaunchFileEntry) {
ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue); ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue);
} }
else if (it->type == MD_CONTROLLER && mMetaData->get(it->key) != "") { else if (it->type == MD_CONTROLLER && mMetaData->get(it->key) != "") {
std::string displayName = BadgeComponent::getDisplayName(mMetaData->get(it->key)); std::string displayName {BadgeComponent::getDisplayName(mMetaData->get(it->key))};
if (displayName != "unknown") if (displayName != "unknown")
ed->setValue(displayName); ed->setValue(displayName);
else else
@ -639,6 +747,9 @@ void GuiMetaDataEd::save()
if (key == "altemulator" && mInvalidEmulatorEntry == true) if (key == "altemulator" && mInvalidEmulatorEntry == true)
continue; continue;
if (key == "launchfile" && mInvalidLaunchFileEntry)
continue;
if (key == "controller" && mEditors.at(i)->getValue() != "") { if (key == "controller" && mEditors.at(i)->getValue() != "") {
std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue()); std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue());
if (shortName != "unknown") if (shortName != "unknown")
@ -807,7 +918,10 @@ void GuiMetaDataEd::close()
const std::string& key {mMetaDataDecl.at(i + offset).key}; const std::string& key {mMetaDataDecl.at(i + offset).key};
if (key == "altemulator" && mInvalidEmulatorEntry == true) if (key == "altemulator" && mInvalidEmulatorEntry)
continue;
if (key == "launchfile" && mInvalidLaunchFileEntry)
continue; continue;
std::string mMetaDataValue {mMetaData->get(key)}; std::string mMetaDataValue {mMetaData->get(key)};

View file

@ -73,6 +73,7 @@ private:
bool mMediaFilesUpdated; bool mMediaFilesUpdated;
bool mSavedMediaAndAborted; bool mSavedMediaAndAborted;
bool mInvalidEmulatorEntry; bool mInvalidEmulatorEntry;
bool mInvalidLaunchFileEntry;
}; };
#endif // ES_APP_GUIS_GUI_META_DATA_ED_H #endif // ES_APP_GUIS_GUI_META_DATA_ED_H

View file

@ -6,6 +6,11 @@
// Gamelist base class with utility functions and other low-level logic. // Gamelist base class with utility functions and other low-level logic.
// //
#if defined(_WIN64)
// Why this is needed here is anyone's guess but without it the compilation fails.
#include <winsock2.h>
#endif
#include "views/GamelistBase.h" #include "views/GamelistBase.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
@ -73,12 +78,33 @@ bool GamelistBase::input(InputConfig* config, Input input)
// It's a folder. // It's a folder.
if (cursor->getChildren().size() > 0) { if (cursor->getChildren().size() > 0) {
ViewController::getInstance()->cancelViewTransitions(); ViewController::getInstance()->cancelViewTransitions();
// If a "launch file" entry has been set on the folder, then check if it
// corresponds to an actual child entry, and if so then launch this child
// instead of entering the folder.
if (!CollectionSystemsManager::getInstance()->isEditing() &&
cursor->metadata.get("launchfile") != "") {
std::string launchFile;
launchFile.append(cursor->getPath())
.append("/")
.append(Utils::String::replace(cursor->metadata.get("launchfile"), "\\",
"/"));
for (auto child : cursor->getChildrenRecursive()) {
if (child->getPath() == launchFile) {
pauseViewVideos();
ViewController::getInstance()->cancelViewTransitions();
stopListScrolling();
launch(child);
return true;
}
}
}
NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
mCursorStack.push(cursor); mCursorStack.push(cursor);
populateList(cursor->getChildrenListToDisplay(), cursor); populateList(cursor->getChildrenListToDisplay(), cursor);
FileData* newCursor {nullptr}; FileData* newCursor {nullptr};
std::vector<FileData*> listEntries = cursor->getChildrenListToDisplay(); std::vector<FileData*> listEntries {cursor->getChildrenListToDisplay()};
// Check if there is an entry in the cursor stack history matching any entry // Check if there is an entry in the cursor stack history matching any entry
// in the currect folder. If so, select that entry. // in the currect folder. If so, select that entry.
for (auto it = mCursorStackHistory.begin(); // Line break. for (auto it = mCursorStackHistory.begin(); // Line break.
@ -469,6 +495,41 @@ bool GamelistBase::input(InputConfig* config, Input input)
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
void GamelistBase::enterDirectory(FileData* cursor)
{
assert(cursor->getType() == 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(); 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);
stopListScrolling();
if (mRoot->getSystem()->getThemeFolder() == "custom-collections")
updateHelpPrompts();
}
else {
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
}
void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* firstEntry) void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* firstEntry)
{ {
mFirstGameEntry = nullptr; mFirstGameEntry = nullptr;
@ -537,10 +598,18 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
} }
else if ((*it)->getType() == FOLDER && else if ((*it)->getType() == FOLDER &&
mRoot->getSystem()->getName() != "collections") { mRoot->getSystem()->getName() != "collections") {
if (Settings::getInstance()->getBool("SpecialCharsASCII")) if (Settings::getInstance()->getBool("SpecialCharsASCII")) {
name = "# " + (*it)->getName(); if ((*it)->metadata.get("launchfile") != "")
else name = "> " + (*it)->getName();
name = ViewController::FOLDER_CHAR + " " + (*it)->getName(); else
name = "# " + (*it)->getName();
}
else {
if ((*it)->metadata.get("launchfile") != "")
name = ViewController::FOLDERLINK_CHAR + " " + (*it)->getName();
else
name = ViewController::FOLDER_CHAR + " " + (*it)->getName();
}
} }
else { else {
name = inCollectionPrefix + (*it)->getName(); name = inCollectionPrefix + (*it)->getName();

View file

@ -34,6 +34,7 @@ public:
void setCursor(FileData*); void setCursor(FileData*);
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void enterDirectory(FileData* cursor);
FileData* getNextEntry() { return mPrimary->getNext(); } FileData* getNextEntry() { return mPrimary->getNext(); }
FileData* getPreviousEntry() { return mPrimary->getPrevious(); } FileData* getPreviousEntry() { return mPrimary->getPrevious(); }

View file

@ -10,8 +10,6 @@
#define ES_APP_VIEWS_GAMELIST_VIEW_H #define ES_APP_VIEWS_GAMELIST_VIEW_H
#include "views/GamelistBase.h" #include "views/GamelistBase.h"
#include "renderers/Renderer.h"
#include "views/ViewController.h" #include "views/ViewController.h"
class GamelistView : public GamelistBase class GamelistView : public GamelistBase

View file

@ -132,6 +132,7 @@ public:
static inline const std::string FAVORITE_CHAR {Utils::String::wideStringToString(L"\uf005")}; static inline const std::string FAVORITE_CHAR {Utils::String::wideStringToString(L"\uf005")};
static inline const std::string FILTER_CHAR {Utils::String::wideStringToString(L"\uf0b0")}; static inline const std::string FILTER_CHAR {Utils::String::wideStringToString(L"\uf0b0")};
static inline const std::string FOLDER_CHAR {Utils::String::wideStringToString(L"\uf07C")}; static inline const std::string FOLDER_CHAR {Utils::String::wideStringToString(L"\uf07C")};
static inline const std::string FOLDERLINK_CHAR {Utils::String::wideStringToString(L"\uf090")};
static inline const std::string GEAR_CHAR {Utils::String::wideStringToString(L"\uf013")}; static inline const std::string GEAR_CHAR {Utils::String::wideStringToString(L"\uf013")};
static inline const std::string KEYBOARD_CHAR {Utils::String::wideStringToString(L"\uf11c")}; static inline const std::string KEYBOARD_CHAR {Utils::String::wideStringToString(L"\uf11c")};
static inline const std::string TICKMARK_CHAR {Utils::String::wideStringToString(L"\uf14A")}; static inline const std::string TICKMARK_CHAR {Utils::String::wideStringToString(L"\uf14A")};
@ -142,6 +143,7 @@ public:
static inline const std::string FAVORITE_CHAR {"\uf005"}; static inline const std::string FAVORITE_CHAR {"\uf005"};
static inline const std::string FILTER_CHAR {"\uf0b0"}; static inline const std::string FILTER_CHAR {"\uf0b0"};
static inline const std::string FOLDER_CHAR {"\uf07C"}; static inline const std::string FOLDER_CHAR {"\uf07C"};
static inline const std::string FOLDERLINK_CHAR {"\uf090"};
static inline const std::string GEAR_CHAR {"\uf013"}; static inline const std::string GEAR_CHAR {"\uf013"};
static inline const std::string KEYBOARD_CHAR {"\uf11c"}; static inline const std::string KEYBOARD_CHAR {"\uf11c"};
static inline const std::string TICKMARK_CHAR {"\uf14a"}; static inline const std::string TICKMARK_CHAR {"\uf14a"};

View file

@ -23,11 +23,6 @@
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#if defined(_WIN64)
// Why this is needed here is anyone's guess but without it the compilation fails.
#include <winsock2.h>
#endif
namespace pugi namespace pugi
{ {
class xml_node; class xml_node;