mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 14:15:38 +00:00
Added support for launching game files inside folders without having to enter the folder.
This commit is contained in:
parent
9c485aeef8
commit
45af7441e9
|
@ -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 &&
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue