diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 9e9a64e04..81697d936 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -748,36 +748,41 @@ void FileData::launchGame(Window* window) LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"..."; std::string command = ""; + std::string alternativeEmulator = getSystem()->getAlternativeEmulator(); - // Check if there is a launch command override for the game - // and the corresponding option to use it has been set. - if (Settings::getInstance()->getBool("LaunchCommandOverride") && - !metadata.get("launchcommand").empty()) { - command = metadata.get("launchcommand"); + // Check if there is a game-specific alternative emulator configured. + // This takes precedence over any system-wide alternative emulator configuration. + if (Settings::getInstance()->getBool("AlternativeEmulatorPerGame") && + !metadata.get("altemulator").empty()) { + command = getSystem()->getLaunchCommandFromLabel(metadata.get("altemulator")); + if (command == "") { + LOG(LogWarning) << "Invalid alternative emulator \"" << metadata.get("altemulator") + << "\" configured for game"; + } + else { + LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \"" + << metadata.get("altemulator") << "\" as configured for the game"; + } } - else { - std::string alternativeEmulator = getSystem()->getAlternativeEmulator(); - for (auto launchCommand : mEnvData->mLaunchCommands) { - if (launchCommand.second == alternativeEmulator) { - command = launchCommand.first; - LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \"" - << alternativeEmulator << "\"" - << " for system \"" << this->getSystem()->getName() << "\""; - break; - } - } - if (!alternativeEmulator.empty() && command.empty()) { - LOG(LogWarning) << "The alternative emulator configured for system \"" - << getSystem()->getName() - << "\" is invalid, falling back to the default command \"" - << getSystem()->getSystemEnvData()->mLaunchCommands.front().first - << "\""; - } - if (command.empty()) - command = mEnvData->mLaunchCommands.front().first; + // Check if there is a system-wide alternative emulator configured. + if (command == "" && alternativeEmulator != "") { + command = getSystem()->getLaunchCommandFromLabel(alternativeEmulator); + if (command == "") { + LOG(LogWarning) << "Invalid alternative emulator \"" + << alternativeEmulator.substr(9, alternativeEmulator.length() - 9) + << "\" configured for system \"" << getSystem()->getName() << "\""; + } + else { + LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \"" + << getSystem()->getAlternativeEmulator() << "\"" + << " as configured for system \"" << this->getSystem()->getName() << "\""; + } } + if (command.empty()) + command = mEnvData->mLaunchCommands.front().first; + std::string commandRaw = command; const std::string romPath = Utils::FileSystem::getEscapedPath(getPath()); diff --git a/es-app/src/Gamelist.cpp b/es-app/src/Gamelist.cpp index 26e2802fe..8ec5da384 100644 --- a/es-app/src/Gamelist.cpp +++ b/es-app/src/Gamelist.cpp @@ -132,7 +132,7 @@ void parseGamelist(SystemData* system) << "\" has a valid alternativeEmulator entry: \"" << label << "\""; } else { - system->setAlternativeEmulator(""); + system->setAlternativeEmulator("" + label); LOG(LogWarning) << "System \"" << system->getName() << "\" has an invalid alternativeEmulator entry that does " "not match any command tag in es_systems.xml: \"" diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 4b8b97432..c514cfb57 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -15,6 +15,8 @@ #include // clang-format off +// The statistic entries must be placed at the bottom or otherwise there will be problems with +// saving the values in GuiMetaDataEd. MetaDataDecl gameDecls[] = { // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape {"name", MD_STRING, "", false, "name", "enter name", true}, @@ -34,9 +36,8 @@ MetaDataDecl gameDecls[] = { {"nogamecount", MD_BOOL, "false", false, "exclude from game counter", "enter don't count as game 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}, -{"launchcommand", MD_LAUNCHCOMMAND, "", false, "launch command", "enter game launch command " - "(emulator override)", false}, {"playcount", MD_INT, "0", false, "times played", "enter number of times played", false}, +{"altemulator", MD_ALT_EMULATOR, "", false, "alternative emulator", "select alternative emulator", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; diff --git a/es-app/src/MetaData.h b/es-app/src/MetaData.h index 1342c1d35..8f0ac5964 100644 --- a/es-app/src/MetaData.h +++ b/es-app/src/MetaData.h @@ -32,7 +32,7 @@ enum MetaDataType { // Specialized types. MD_MULTILINE_STRING, - MD_LAUNCHCOMMAND, + MD_ALT_EMULATOR, MD_PATH, MD_RATING, MD_DATE, diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 78eee2c16..431ebbaac 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -501,10 +501,9 @@ bool SystemData::loadConfig() } else if (!commands.empty() && commands.back().second == "") { // There are more than one command tags and the first tag did not have a label. - LOG(LogError) - << "Missing mandatory label attribute for alternative emulator " - "entry, only the first command tag will be processed for system \"" - << name << "\""; + LOG(LogError) << "Missing mandatory label attribute for alternative emulator " + "entry, only the first command tag will be processed for system \"" + << name << "\""; break; } commands.push_back( @@ -615,6 +614,18 @@ bool SystemData::loadConfig() return false; } +std::string SystemData::getLaunchCommandFromLabel(const std::string& label) +{ + auto commandIter = std::find_if( + mEnvData->mLaunchCommands.cbegin(), mEnvData->mLaunchCommands.cend(), + [label](std::pair command) { return (command.second == label); }); + + if (commandIter != mEnvData->mLaunchCommands.cend()) + return (*commandIter).first; + + return ""; +} + void SystemData::deleteSystems() { for (unsigned int i = 0; i < sSystemVector.size(); i++) diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index 5927c2385..0b8c7b17c 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -99,6 +99,7 @@ public: std::string getAlternativeEmulator() { return mAlternativeEmulator; } void setAlternativeEmulator(const std::string& command) { mAlternativeEmulator = command; } + std::string getLaunchCommandFromLabel(const std::string& label); static void deleteSystems(); // Loads the systems configuration file at getConfigPath() and creates the systems. diff --git a/es-app/src/guis/GuiAlternativeEmulators.cpp b/es-app/src/guis/GuiAlternativeEmulators.cpp index a2ab49a86..a3fcd0e59 100644 --- a/es-app/src/guis/GuiAlternativeEmulators.cpp +++ b/es-app/src/guis/GuiAlternativeEmulators.cpp @@ -30,7 +30,7 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) // Only include systems that have at least two command entries, unless the system // has an invalid entry. - if ((*it)->getAlternativeEmulator() != "" && + if ((*it)->getAlternativeEmulator().substr(0, 9) != "" && (*it)->getSystemEnvData()->mLaunchCommands.size() < 2) continue; @@ -68,7 +68,7 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) bool invalidEntry = false; if (label.empty()) { - label = ""; + label = ViewController::EXCLAMATION_CHAR + " INVALID ENTRY"; invalidEntry = true; } @@ -94,7 +94,11 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) labelText->setSize(labelSizeX, labelText->getSize().y); row.addElement(labelText, false); - row.makeAcceptInputHandler([this, it] { selectorWindow(*it); }); + row.makeAcceptInputHandler([this, it, labelText] { + if (labelText->getValue() == "") + return; + selectorWindow(*it); + }); mMenu.addRow(row); mHasSystems = true; @@ -104,9 +108,9 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) // es_systems.xml. if (!mHasSystems) { ComponentListRow row; - std::shared_ptr systemText = - std::make_shared(mWindow, "", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); + std::shared_ptr systemText = std::make_shared( + mWindow, ViewController::EXCLAMATION_CHAR + " NO ALTERNATIVE EMULATORS DEFINED", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); row.addElement(systemText, true); mMenu.addRow(row); } @@ -150,13 +154,16 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) ComponentListRow row; if (entry.second == "") - label = ""; + label = ""; else label = entry.second; std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); + if (system->getSystemEnvData()->mLaunchCommands.front().second == label) + labelText->setValue(labelText->getValue().append(" [DEFAULT]")); + row.addElement(labelText, true); row.makeAcceptInputHandler([this, s, system, labelText, entry, selectedLabel] { if (entry.second != selectedLabel) { @@ -165,9 +172,25 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) else system->setAlternativeEmulator(entry.second); updateGamelist(system, true); - updateMenu( - system->getName(), labelText->getValue(), - (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second)); + + if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) { + if (system->getSystemEnvData()->mLaunchCommands.front().second == "") { + updateMenu(system->getName(), "", + (entry.second == + system->getSystemEnvData()->mLaunchCommands.front().second)); + } + else { + updateMenu(system->getName(), + system->getSystemEnvData()->mLaunchCommands.front().second, + (entry.second == + system->getSystemEnvData()->mLaunchCommands.front().second)); + } + } + else { + updateMenu(system->getName(), labelText->getValue(), + (entry.second == + system->getSystemEnvData()->mLaunchCommands.front().second)); + } } delete s; }); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index e77910616..27adc87c8 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -1021,16 +1021,17 @@ void GuiMenu::openOtherOptions() } }); - // Allow overriding of the launch command per game (the option to disable this is - // intended primarily for testing purposes). - auto launchcommand_override = std::make_shared(mWindow); - launchcommand_override->setState(Settings::getInstance()->getBool("LaunchCommandOverride")); - s->addWithLabel("PER GAME LAUNCH COMMAND OVERRIDE", launchcommand_override); - s->addSaveFunc([launchcommand_override, s] { - if (launchcommand_override->getState() != - Settings::getInstance()->getBool("LaunchCommandOverride")) { - Settings::getInstance()->setBool("LaunchCommandOverride", - launchcommand_override->getState()); + // Whether to enable alternative emulators per game (the option to disable this is intended + // primarily for testing purposes). + auto alternativeEmulatorPerGame = std::make_shared(mWindow); + alternativeEmulatorPerGame->setState( + Settings::getInstance()->getBool("AlternativeEmulatorPerGame")); + s->addWithLabel("ENABLE ALTERNATIVE EMULATORS PER GAME", alternativeEmulatorPerGame); + s->addSaveFunc([alternativeEmulatorPerGame, s] { + if (alternativeEmulatorPerGame->getState() != + Settings::getInstance()->getBool("AlternativeEmulatorPerGame")) { + Settings::getInstance()->setBool("AlternativeEmulatorPerGame", + alternativeEmulatorPerGame->getState()); s->setNeedsSaving(); } }); diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 3ce434094..c5d4168a3 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -49,6 +49,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, , mClearGameFunc(clearGameFunc) , mDeleteGameFunc(deleteGameFunc) , mMediaFilesUpdated(false) + , mInvalidEmulatorEntry(false) { addChild(&mBackground); addChild(&mGrid); @@ -99,9 +100,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, if (iter->isStatistic) continue; - // Don't show the launch command override entry if this option has been disabled. - if (!Settings::getInstance()->getBool("LaunchCommandOverride") && - iter->type == MD_LAUNCHCOMMAND) { + // Don't show the alternative emulator entry if the corresponding option has been disabled. + if (!Settings::getInstance()->getBool("AlternativeEmulatorPerGame") && + iter->type == MD_ALT_EMULATOR) { ed = std::make_shared( window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); assert(ed); @@ -170,7 +171,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::placeholders::_2); break; } - case MD_LAUNCHCOMMAND: { + case MD_ALT_EMULATOR: { + mInvalidEmulatorEntry = false; + ed = std::make_shared(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); @@ -189,44 +192,124 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, const std::string title = iter->displayPrompt; // OK callback (apply new value to ed). - auto updateVal = [ed, originalValue](const std::string& newVal) { + auto updateVal = [this, ed, originalValue](const std::string& newVal) { ed->setValue(newVal); - if (newVal == originalValue) + if (newVal == originalValue) { ed->setColor(DEFAULT_TEXTCOLOR); - else + } + else { ed->setColor(TEXTCOLOR_USERMARKED); + mInvalidEmulatorEntry = false; + } }; - std::string staticTextString = "Default value from es_systems.xml:"; - std::string defaultLaunchCommand; - - std::string alternativeEmulator = scraperParams.system->getAlternativeEmulator(); - for (auto launchCommand : - scraperParams.system->getSystemEnvData()->mLaunchCommands) { - if (launchCommand.second == alternativeEmulator) { - defaultLaunchCommand = launchCommand.first; - break; - } - } - if (!alternativeEmulator.empty() && defaultLaunchCommand.empty()) { + if (originalValue != "" && + scraperParams.system->getLaunchCommandFromLabel(originalValue) == "") { LOG(LogWarning) - << "The alternative emulator defined for system \"" - << scraperParams.system->getName() - << "\" is invalid, falling back to the default command \"" - << scraperParams.system->getSystemEnvData()->mLaunchCommands.front().first - << "\""; + << "GuiMetaDataEd: Invalid alternative emulator \"" << originalValue + << "\" configured for game \"" << mScraperParams.game->getName() << "\""; + mInvalidEmulatorEntry = true; } - if (defaultLaunchCommand.empty()) - defaultLaunchCommand = - scraperParams.system->getSystemEnvData()->mLaunchCommands.front().first; + if (scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1) { + lbl->setOpacity(DISABLED_OPACITY); + bracket->setOpacity(DISABLED_OPACITY); + } - row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchCommand, ed, - updateVal, multiLine] { - mWindow->pushGui(new GuiComplexTextEditPopup( - mWindow, getHelpStyle(), title, staticTextString, defaultLaunchCommand, - ed->getValue(), updateVal, multiLine, "APPLY", "APPLY CHANGES?")); - }); + if (mInvalidEmulatorEntry || + scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) { + row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal] { + auto s = new GuiSettings(mWindow, title); + + if (!mInvalidEmulatorEntry && ed->getValue() == "" && + scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1) + return; + + std::vector> launchCommands = + scraperParams.system->getSystemEnvData()->mLaunchCommands; + + if (ed->getValue() != "" && mInvalidEmulatorEntry) + launchCommands.push_back(std::make_pair("", "")); + else if (ed->getValue() != "") + launchCommands.push_back(std::make_pair("", "")); + + for (auto entry : launchCommands) { + std::string selectedLabel = ed->getValue(); + std::string label; + ComponentListRow row; + + if (entry.second == "") + continue; + else + label = entry.second; + + std::shared_ptr labelText = + std::make_shared(mWindow, label, + Font::get(FONT_SIZE_MEDIUM), + 0x777777FF, ALIGN_CENTER); + + if (scraperParams.system->getAlternativeEmulator() == "" && + scraperParams.system->getSystemEnvData() + ->mLaunchCommands.front() + .second == label) + labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]")); + + if (scraperParams.system->getAlternativeEmulator() == label) + labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]")); + + row.addElement(labelText, true); + row.makeAcceptInputHandler( + [this, s, updateVal, entry, selectedLabel, launchCommands] { + if (entry.second == launchCommands.back().second && + launchCommands.back().first == "") { + updateVal(""); + } + else if (entry.second != selectedLabel) { + updateVal(entry.second); + } + mInvalidEmulatorEntry = false; + delete s; + }); + + // This transparent bracket is only added to generate the correct help + // prompts. + auto bracket = std::make_shared(mWindow); + bracket->setImage(":/graphics/arrow.svg"); + bracket->setOpacity(0); + bracket->setSize(bracket->getSize() / 3.0f); + row.addElement(bracket, false); + + // Select the row that corresponds to the selected label. + if (selectedLabel == label) + s->addRow(row, true); + else + s->addRow(row, false); + } + + // Adjust the width depending on the aspect ratio of the screen, to make the + // screen look somewhat coherent regardless of screen type. The 1.778 aspect + // ratio value is the 16:9 reference. + float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); + + float maxWidthModifier = glm::clamp(0.70f * aspectValue, 0.50f, 0.92f); + float maxWidth = + static_cast(Renderer::getScreenWidth()) * maxWidthModifier; + + s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y}); + + auto menuSize = s->getMenuSize(); + auto menuPos = s->getMenuPosition(); + + s->setMenuPosition(glm::vec3{(s->getSize().x - menuSize.x) / 2.0f, + (s->getSize().y - menuSize.y) / 3.0f, + menuPos.z}); + mWindow->pushGui(s); + }); + } + else { + lbl->setOpacity(DISABLED_OPACITY); + bracket->setOpacity(DISABLED_OPACITY); + } break; } case MD_MULTILINE_STRING: @@ -292,7 +375,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, assert(ed); mList->addRow(row); - ed->setValue(mMetaData->get(iter->key)); + + if (iter->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) + ed->setValue(ViewController::EXCLAMATION_CHAR + " INVALID ENTRY "); + else + ed->setValue(mMetaData->get(iter->key)); + mEditors.push_back(ed); } @@ -435,6 +523,9 @@ void GuiMetaDataEd::save() if (mMetaDataDecl.at(i).isStatistic) continue; + if (mMetaDataDecl.at(i).key == "altemulator" && mInvalidEmulatorEntry == true) + continue; + if (!showHiddenGames && mMetaDataDecl.at(i).key == "hidden" && mEditors.at(i)->getValue() != mMetaData->get("hidden")) hideGameWhileHidden = true; @@ -576,6 +667,9 @@ void GuiMetaDataEd::close() std::string mMetaDataValue = mMetaData->get(key); std::string mEditorsValue = mEditors.at(i)->getValue(); + if (key == "altemulator" && mInvalidEmulatorEntry == true) + continue; + if (mMetaDataValue != mEditorsValue) { metadataUpdated = true; break; diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index e770b4501..91d781996 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -15,6 +15,7 @@ #include "MetaData.h" #include "components/ComponentGrid.h" #include "components/NinePatchComponent.h" +#include "guis/GuiSettings.h" #include "scrapers/Scraper.h" class ComponentList; @@ -64,6 +65,7 @@ private: std::function mDeleteGameFunc; bool mMediaFilesUpdated; + bool mInvalidEmulatorEntry; }; #endif // ES_APP_GUIS_GUI_META_DATA_ED_H diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index d597706f9..46ed7ebe3 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -617,7 +617,7 @@ int main(int argc, char* argv[]) // which means that a label is present in the gamelist.xml file which is not matching // any command tag in es_systems.xml. for (auto system : SystemData::sSystemVector) { - if (system->getAlternativeEmulator() == "") { + if (system->getAlternativeEmulator().substr(0, 9) == "") { ViewController::get()->invalidAlternativeEmulatorDialog(); break; } diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 44254abe5..0227b92cb 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -205,7 +205,7 @@ void ViewController::invalidAlternativeEmulatorDialog() "WITH NO MATCHING ENTRY IN THE SYSTEMS\n" "CONFIGURATION FILE, PLEASE REVIEW YOUR\n" "SETUP USING THE 'ALTERNATIVE EMULATORS'\n" - "ENTRY UNDER THE 'OTHER SETTINGS' MENU")); + "INTERFACE IN THE 'OTHER SETTINGS' MENU")); } void ViewController::goToStart() diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 845fac177..9e15920c6 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -240,7 +240,7 @@ void Settings::setDefaults() mBoolMap["VideoHardwareDecoding"] = {false, false}; #endif mBoolMap["VideoUpscaleFrameRate"] = {false, false}; - mBoolMap["LaunchCommandOverride"] = {true, true}; + mBoolMap["AlternativeEmulatorPerGame"] = {true, true}; mBoolMap["ShowHiddenFiles"] = {true, true}; mBoolMap["ShowHiddenGames"] = {true, true}; mBoolMap["CustomEventScripts"] = {false, false};