mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 07:35:38 +00:00
Added support for defining and choosing between alternative emulators.
This commit is contained in:
parent
7b111807ae
commit
5381f38231
|
@ -16,6 +16,7 @@ set(ES_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
|
||||||
|
|
||||||
# GUIs
|
# GUIs
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
|
||||||
|
@ -66,6 +67,7 @@ set(ES_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
||||||
|
|
||||||
# GUIs
|
# GUIs
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp
|
||||||
|
|
|
@ -73,7 +73,8 @@ CollectionSystemsManager::CollectionSystemsManager(Window* window)
|
||||||
mCollectionEnvData->mStartPath = "";
|
mCollectionEnvData->mStartPath = "";
|
||||||
std::vector<std::string> exts;
|
std::vector<std::string> exts;
|
||||||
mCollectionEnvData->mSearchExtensions = exts;
|
mCollectionEnvData->mSearchExtensions = exts;
|
||||||
mCollectionEnvData->mLaunchCommand = "";
|
std::vector<std::pair<std::string, std::string>> commands;
|
||||||
|
mCollectionEnvData->mLaunchCommands = commands;
|
||||||
std::vector<PlatformIds::PlatformId> allPlatformIds;
|
std::vector<PlatformIds::PlatformId> allPlatformIds;
|
||||||
allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE);
|
allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE);
|
||||||
mCollectionEnvData->mPlatformIds = allPlatformIds;
|
mCollectionEnvData->mPlatformIds = allPlatformIds;
|
||||||
|
|
|
@ -756,7 +756,22 @@ void FileData::launchGame(Window* window)
|
||||||
command = metadata.get("launchcommand");
|
command = metadata.get("launchcommand");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
command = mEnvData->mLaunchCommand;
|
std::string alternativeEmulator = getSystem()->getAlternativeEmulator();
|
||||||
|
for (auto launchCommand : mEnvData->mLaunchCommands) {
|
||||||
|
if (launchCommand.second == alternativeEmulator) {
|
||||||
|
command = launchCommand.first;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string commandRaw = command;
|
std::string commandRaw = command;
|
||||||
|
|
|
@ -117,6 +117,30 @@ void parseGamelist(SystemData* system)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||||
|
if (alternativeEmulator) {
|
||||||
|
std::string label = alternativeEmulator.child("label").text().get();
|
||||||
|
if (label != "") {
|
||||||
|
bool validLabel = false;
|
||||||
|
for (auto command : system->getSystemEnvData()->mLaunchCommands) {
|
||||||
|
if (command.second == label)
|
||||||
|
validLabel = true;
|
||||||
|
}
|
||||||
|
if (validLabel) {
|
||||||
|
system->setAlternativeEmulator(label);
|
||||||
|
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
|
||||||
|
<< "\" has a valid alternativeEmulator entry: \"" << label << "\"";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
system->setAlternativeEmulator("<INVALID>");
|
||||||
|
LOG(LogWarning) << "System \"" << system->getName()
|
||||||
|
<< "\" has an invalid alternativeEmulator entry that does "
|
||||||
|
"not match any command tag in es_systems.xml: \""
|
||||||
|
<< label << "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string relativeTo = system->getStartPath();
|
std::string relativeTo = system->getStartPath();
|
||||||
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
|
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
|
||||||
|
|
||||||
|
@ -221,7 +245,7 @@ void addFileDataNode(pugi::xml_node& parent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateGamelist(SystemData* system)
|
void updateGamelist(SystemData* system, bool updateAlternativeEmulator)
|
||||||
{
|
{
|
||||||
// We do this by reading the XML again, adding changes and then writing them back,
|
// We do this by reading the XML again, adding changes and then writing them back,
|
||||||
// because there might be information missing in our systemdata which we would otherwise
|
// because there might be information missing in our systemdata which we would otherwise
|
||||||
|
@ -256,8 +280,39 @@ void updateGamelist(SystemData* system)
|
||||||
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath << "\"";
|
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath << "\"";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (updateAlternativeEmulator) {
|
||||||
|
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||||
|
|
||||||
|
if (system->getAlternativeEmulator() != "") {
|
||||||
|
if (!alternativeEmulator) {
|
||||||
|
doc.prepend_child("alternativeEmulator");
|
||||||
|
alternativeEmulator = doc.child("alternativeEmulator");
|
||||||
|
}
|
||||||
|
|
||||||
|
pugi::xml_node label = alternativeEmulator.child("label");
|
||||||
|
|
||||||
|
if (label && system->getAlternativeEmulator() !=
|
||||||
|
alternativeEmulator.child("label").text().get()) {
|
||||||
|
alternativeEmulator.remove_child(label);
|
||||||
|
alternativeEmulator.prepend_child("label").text().set(
|
||||||
|
system->getAlternativeEmulator().c_str());
|
||||||
|
}
|
||||||
|
else if (!label) {
|
||||||
|
alternativeEmulator.prepend_child("label").text().set(
|
||||||
|
system->getAlternativeEmulator().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (alternativeEmulator) {
|
||||||
|
doc.remove_child("alternativeEmulator");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (updateAlternativeEmulator && system->getAlternativeEmulator() != "") {
|
||||||
|
pugi::xml_node alternativeEmulator = doc.prepend_child("alternativeEmulator");
|
||||||
|
alternativeEmulator.prepend_child("label").text().set(
|
||||||
|
system->getAlternativeEmulator().c_str());
|
||||||
|
}
|
||||||
// Set up an empty gamelist to append to.
|
// Set up an empty gamelist to append to.
|
||||||
root = doc.append_child("gameList");
|
root = doc.append_child("gameList");
|
||||||
}
|
}
|
||||||
|
@ -312,15 +367,31 @@ void updateGamelist(SystemData* system)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now write the file.
|
// Now write the file.
|
||||||
if (numUpdated > 0) {
|
if (numUpdated > 0 || updateAlternativeEmulator) {
|
||||||
// Make sure the folders leading up to this path exist (or the write will fail).
|
// Make sure the folders leading up to this path exist (or the write will fail).
|
||||||
std::string xmlWritePath(system->getGamelistPath(true));
|
std::string xmlWritePath(system->getGamelistPath(true));
|
||||||
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
|
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
|
||||||
|
|
||||||
|
if (updateAlternativeEmulator) {
|
||||||
|
if (system->getAlternativeEmulator() == "") {
|
||||||
|
LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the "
|
||||||
|
"alternativeEmulator tag for system \""
|
||||||
|
<< system->getName() << "\" as the default emulator \""
|
||||||
|
<< system->getSystemEnvData()->mLaunchCommands.front().second
|
||||||
|
<< "\" was selected";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG(LogDebug) << "Gamelist::updateGamelist(): "
|
||||||
|
"Added/updated the alternativeEmulator tag for system \""
|
||||||
|
<< system->getName() << "\" to \""
|
||||||
|
<< system->getAlternativeEmulator() << "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numUpdated > 0) {
|
||||||
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
|
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
|
||||||
<< (numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlReadPath
|
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
|
||||||
<< "\"";
|
<< xmlWritePath << "\"";
|
||||||
|
}
|
||||||
#if defined(_WIN64)
|
#if defined(_WIN64)
|
||||||
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
|
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -15,6 +15,6 @@ class SystemData;
|
||||||
void parseGamelist(SystemData* system);
|
void parseGamelist(SystemData* system);
|
||||||
|
|
||||||
// Writes currently loaded metadata for a SystemData to gamelist.xml.
|
// Writes currently loaded metadata for a SystemData to gamelist.xml.
|
||||||
void updateGamelist(SystemData* system);
|
void updateGamelist(SystemData* system, bool updateAlternativeEmulator = false);
|
||||||
|
|
||||||
#endif // ES_APP_GAME_LIST_H
|
#endif // ES_APP_GAME_LIST_H
|
||||||
|
|
|
@ -417,13 +417,27 @@ bool SystemData::loadConfig()
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string fullname;
|
std::string fullname;
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string cmd;
|
|
||||||
std::string themeFolder;
|
std::string themeFolder;
|
||||||
|
|
||||||
name = system.child("name").text().get();
|
name = system.child("name").text().get();
|
||||||
fullname = system.child("fullname").text().get();
|
fullname = system.child("fullname").text().get();
|
||||||
path = system.child("path").text().get();
|
path = system.child("path").text().get();
|
||||||
|
|
||||||
|
auto nameFindFunc = [&] {
|
||||||
|
for (auto system : sSystemVector) {
|
||||||
|
if (system->mName == name) {
|
||||||
|
LOG(LogWarning) << "A system with the name \"" << name
|
||||||
|
<< "\" has already been loaded, skipping duplicate entry";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the name is matching a system that has already been loaded, then skip the entry.
|
||||||
|
if (nameFindFunc())
|
||||||
|
continue;
|
||||||
|
|
||||||
// If there is a %ROMPATH% variable set for the system, expand it. By doing this
|
// If there is a %ROMPATH% variable set for the system, expand it. By doing this
|
||||||
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize
|
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize
|
||||||
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to ""
|
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to ""
|
||||||
|
@ -461,7 +475,41 @@ bool SystemData::loadConfig()
|
||||||
// Convert extensions list from a string into a vector of strings.
|
// Convert extensions list from a string into a vector of strings.
|
||||||
std::vector<std::string> extensions = readList(system.child("extension").text().get());
|
std::vector<std::string> extensions = readList(system.child("extension").text().get());
|
||||||
|
|
||||||
cmd = system.child("command").text().get();
|
// Load all launch command tags for the system and if there are multiple tags, then
|
||||||
|
// the label attribute needs to be set on all entries as it's a requirement for the
|
||||||
|
// alternative emulator logic.
|
||||||
|
std::vector<std::pair<std::string, std::string>> commands;
|
||||||
|
for (pugi::xml_node entry = system.child("command"); entry;
|
||||||
|
entry = entry.next_sibling("command")) {
|
||||||
|
if (!entry.attribute("label")) {
|
||||||
|
if (commands.size() == 1) {
|
||||||
|
// The first command tag had a label but the second one doesn't.
|
||||||
|
LOG(LogError)
|
||||||
|
<< "Missing mandatory label attribute for alternative emulator "
|
||||||
|
"entry, only the default command tag will be processed for system \""
|
||||||
|
<< name << "\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (commands.size() > 1) {
|
||||||
|
// At least two command tags had a label but this one doesn't.
|
||||||
|
LOG(LogError)
|
||||||
|
<< "Missing mandatory label attribute for alternative emulator "
|
||||||
|
"entry, no additional command tags will be processed for system \""
|
||||||
|
<< name << "\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 default command tag will be processed for system \""
|
||||||
|
<< name << "\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
commands.push_back(
|
||||||
|
std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
|
||||||
|
}
|
||||||
|
|
||||||
// Platform ID list
|
// Platform ID list
|
||||||
const std::string platformList =
|
const std::string platformList =
|
||||||
|
@ -504,7 +552,7 @@ bool SystemData::loadConfig()
|
||||||
<< "A system in the es_systems.xml file has no name defined, skipping entry";
|
<< "A system in the es_systems.xml file has no name defined, skipping entry";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (fullname.empty() || path.empty() || extensions.empty() || cmd.empty()) {
|
else if (fullname.empty() || path.empty() || extensions.empty() || commands.empty()) {
|
||||||
LOG(LogError) << "System \"" << name
|
LOG(LogError) << "System \"" << name
|
||||||
<< "\" is missing the fullname, path, "
|
<< "\" is missing the fullname, path, "
|
||||||
"extension, or command tag, skipping entry";
|
"extension, or command tag, skipping entry";
|
||||||
|
@ -526,7 +574,7 @@ bool SystemData::loadConfig()
|
||||||
SystemEnvironmentData* envData = new SystemEnvironmentData;
|
SystemEnvironmentData* envData = new SystemEnvironmentData;
|
||||||
envData->mStartPath = path;
|
envData->mStartPath = path;
|
||||||
envData->mSearchExtensions = extensions;
|
envData->mSearchExtensions = extensions;
|
||||||
envData->mLaunchCommand = cmd;
|
envData->mLaunchCommands = commands;
|
||||||
envData->mPlatformIds = platformIds;
|
envData->mPlatformIds = platformIds;
|
||||||
|
|
||||||
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
|
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
|
||||||
|
@ -679,7 +727,7 @@ bool SystemData::createSystemDirectories()
|
||||||
std::string fullname;
|
std::string fullname;
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string extensions;
|
std::string extensions;
|
||||||
std::string command;
|
std::vector<std::string> commands;
|
||||||
std::string platform;
|
std::string platform;
|
||||||
std::string themeFolder;
|
std::string themeFolder;
|
||||||
const std::string systemInfoFileName = "/systeminfo.txt";
|
const std::string systemInfoFileName = "/systeminfo.txt";
|
||||||
|
@ -690,7 +738,10 @@ bool SystemData::createSystemDirectories()
|
||||||
fullname = system.child("fullname").text().get();
|
fullname = system.child("fullname").text().get();
|
||||||
path = system.child("path").text().get();
|
path = system.child("path").text().get();
|
||||||
extensions = system.child("extension").text().get();
|
extensions = system.child("extension").text().get();
|
||||||
command = system.child("command").text().get();
|
for (pugi::xml_node entry = system.child("command"); entry;
|
||||||
|
entry = entry.next_sibling("command")) {
|
||||||
|
commands.push_back(entry.text().get());
|
||||||
|
}
|
||||||
platform = Utils::String::toLower(system.child("platform").text().get());
|
platform = Utils::String::toLower(system.child("platform").text().get());
|
||||||
themeFolder = system.child("theme").text().as_string(name.c_str());
|
themeFolder = system.child("theme").text().as_string(name.c_str());
|
||||||
|
|
||||||
|
@ -757,7 +808,16 @@ bool SystemData::createSystemDirectories()
|
||||||
systemInfoFile << "Supported file extensions:" << std::endl;
|
systemInfoFile << "Supported file extensions:" << std::endl;
|
||||||
systemInfoFile << extensions << std::endl << std::endl;
|
systemInfoFile << extensions << std::endl << std::endl;
|
||||||
systemInfoFile << "Launch command:" << std::endl;
|
systemInfoFile << "Launch command:" << std::endl;
|
||||||
systemInfoFile << command << std::endl << std::endl;
|
systemInfoFile << commands.front() << std::endl << std::endl;
|
||||||
|
// Alternative emulator configuration entries.
|
||||||
|
if (commands.size() > 1) {
|
||||||
|
systemInfoFile << (commands.size() == 2 ? "Alternative launch command:" :
|
||||||
|
"Alternative launch commands:")
|
||||||
|
<< std::endl;
|
||||||
|
for (auto it = commands.cbegin() + 1; it != commands.cend(); it++)
|
||||||
|
systemInfoFile << (*it) << std::endl;
|
||||||
|
systemInfoFile << std::endl;
|
||||||
|
}
|
||||||
systemInfoFile << "Platform (for scraping):" << std::endl;
|
systemInfoFile << "Platform (for scraping):" << std::endl;
|
||||||
systemInfoFile << platform << std::endl << std::endl;
|
systemInfoFile << platform << std::endl << std::endl;
|
||||||
systemInfoFile << "Theme folder:" << std::endl;
|
systemInfoFile << "Theme folder:" << std::endl;
|
||||||
|
|
|
@ -27,7 +27,7 @@ class ThemeData;
|
||||||
struct SystemEnvironmentData {
|
struct SystemEnvironmentData {
|
||||||
std::string mStartPath;
|
std::string mStartPath;
|
||||||
std::vector<std::string> mSearchExtensions;
|
std::vector<std::string> mSearchExtensions;
|
||||||
std::string mLaunchCommand;
|
std::vector<std::pair<std::string, std::string>> mLaunchCommands;
|
||||||
std::vector<PlatformIds::PlatformId> mPlatformIds;
|
std::vector<PlatformIds::PlatformId> mPlatformIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,6 +97,9 @@ public:
|
||||||
bool getScrapeFlag() { return mScrapeFlag; }
|
bool getScrapeFlag() { return mScrapeFlag; }
|
||||||
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
|
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
|
||||||
|
|
||||||
|
std::string getAlternativeEmulator() { return mAlternativeEmulator; }
|
||||||
|
void setAlternativeEmulator(const std::string& command) { mAlternativeEmulator = command; }
|
||||||
|
|
||||||
static void deleteSystems();
|
static void deleteSystems();
|
||||||
// Loads the systems configuration file at getConfigPath() and creates the systems.
|
// Loads the systems configuration file at getConfigPath() and creates the systems.
|
||||||
static bool loadConfig();
|
static bool loadConfig();
|
||||||
|
@ -153,6 +156,7 @@ private:
|
||||||
std::string mName;
|
std::string mName;
|
||||||
std::string mFullName;
|
std::string mFullName;
|
||||||
SystemEnvironmentData* mEnvData;
|
SystemEnvironmentData* mEnvData;
|
||||||
|
std::string mAlternativeEmulator;
|
||||||
std::string mThemeFolder;
|
std::string mThemeFolder;
|
||||||
std::shared_ptr<ThemeData> mTheme;
|
std::shared_ptr<ThemeData> mTheme;
|
||||||
|
|
||||||
|
|
231
es-app/src/guis/GuiAlternativeEmulators.cpp
Normal file
231
es-app/src/guis/GuiAlternativeEmulators.cpp
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// EmulationStation Desktop Edition
|
||||||
|
// GuiAlternativeEmulators.cpp
|
||||||
|
//
|
||||||
|
// User interface to select between alternative emulators per system
|
||||||
|
// based on configuration entries in es_systems.xml.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "guis/GuiAlternativeEmulators.h"
|
||||||
|
|
||||||
|
#include "Gamelist.h"
|
||||||
|
#include "SystemData.h"
|
||||||
|
#include "views/ViewController.h"
|
||||||
|
|
||||||
|
GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window)
|
||||||
|
: GuiComponent{window}
|
||||||
|
, mMenu{window, "ALTERNATIVE EMULATORS"}
|
||||||
|
, mHasSystems(false)
|
||||||
|
{
|
||||||
|
addChild(&mMenu);
|
||||||
|
mMenu.addButton("BACK", "back", [this] { delete this; });
|
||||||
|
|
||||||
|
// Horizontal sizes for the system and label entries.
|
||||||
|
float systemSizeX = mMenu.getSize().x / 3.27f;
|
||||||
|
float labelSizeX = mMenu.getSize().x / 1.53;
|
||||||
|
|
||||||
|
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
|
||||||
|
it != SystemData::sSystemVector.cend(); it++) {
|
||||||
|
|
||||||
|
// Only include systems that have at least two command entries, unless the system
|
||||||
|
// has an invalid entry.
|
||||||
|
if ((*it)->getAlternativeEmulator() != "<INVALID>" &&
|
||||||
|
(*it)->getSystemEnvData()->mLaunchCommands.size() < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ComponentListRow row;
|
||||||
|
|
||||||
|
// This transparent bracket is only added to generate a left margin.
|
||||||
|
auto bracket = std::make_shared<ImageComponent>(mWindow);
|
||||||
|
bracket->setImage(":/graphics/arrow.svg");
|
||||||
|
bracket->setOpacity(0);
|
||||||
|
bracket->setSize(bracket->getSize() / 3.0f);
|
||||||
|
row.addElement(bracket, false);
|
||||||
|
|
||||||
|
std::string name = (*it)->getName();
|
||||||
|
std::shared_ptr<TextComponent> systemText =
|
||||||
|
std::make_shared<TextComponent>(mWindow, name, Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
||||||
|
|
||||||
|
systemText->setSize(systemSizeX, systemText->getSize().y);
|
||||||
|
row.addElement(systemText, false);
|
||||||
|
|
||||||
|
std::string configuredLabel = (*it)->getAlternativeEmulator();
|
||||||
|
std::string label;
|
||||||
|
|
||||||
|
if (configuredLabel == "") {
|
||||||
|
label = (*it)->getSystemEnvData()->mLaunchCommands.front().second;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (auto command : (*it)->getSystemEnvData()->mLaunchCommands) {
|
||||||
|
if (command.second == configuredLabel) {
|
||||||
|
label = command.second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool invalidEntry = false;
|
||||||
|
|
||||||
|
if (label.empty()) {
|
||||||
|
label = "<INVALID ENTRY>";
|
||||||
|
invalidEntry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TextComponent> labelText;
|
||||||
|
|
||||||
|
if (label == (*it)->getSystemEnvData()->mLaunchCommands.front().second) {
|
||||||
|
labelText = std::make_shared<TextComponent>(
|
||||||
|
mWindow, label, Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT), 0x777777FF,
|
||||||
|
ALIGN_RIGHT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Mark any non-default value with bold and add a gear symbol as well.
|
||||||
|
labelText = std::make_shared<TextComponent>(
|
||||||
|
mWindow, label + (!invalidEntry ? " " + ViewController::GEAR_CHAR : ""),
|
||||||
|
Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD), 0x777777FF, ALIGN_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark invalid entries with red color.
|
||||||
|
if (invalidEntry)
|
||||||
|
labelText->setColor(TEXTCOLOR_SCRAPERMARKED);
|
||||||
|
|
||||||
|
mCommandRows[name] = labelText;
|
||||||
|
labelText->setSize(labelSizeX, labelText->getSize().y);
|
||||||
|
|
||||||
|
row.addElement(labelText, false);
|
||||||
|
row.makeAcceptInputHandler([this, it] { selectorWindow(*it); });
|
||||||
|
|
||||||
|
mMenu.addRow(row);
|
||||||
|
mHasSystems = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a dummy row if no enabled systems have any alternative emulators defined in
|
||||||
|
// es_systems.xml.
|
||||||
|
if (!mHasSystems) {
|
||||||
|
ComponentListRow row;
|
||||||
|
std::shared_ptr<TextComponent> systemText =
|
||||||
|
std::make_shared<TextComponent>(mWindow, "<NO ALTERNATIVE EMULATORS DEFINED>",
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
||||||
|
row.addElement(systemText, true);
|
||||||
|
mMenu.addRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
float width =
|
||||||
|
static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
|
||||||
|
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
|
||||||
|
|
||||||
|
setSize(mMenu.getSize());
|
||||||
|
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, Renderer::getScreenHeight() * 0.13f);
|
||||||
|
|
||||||
|
mMenu.setSize(width, Renderer::getScreenHeight() * 0.76f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiAlternativeEmulators::updateMenu(const std::string& systemName,
|
||||||
|
const std::string& label,
|
||||||
|
bool defaultEmulator)
|
||||||
|
{
|
||||||
|
// If another label was selected, then update the menu accordingly.
|
||||||
|
if (defaultEmulator) {
|
||||||
|
mCommandRows[systemName].get()->setFont(Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT));
|
||||||
|
mCommandRows[systemName].get()->setValue(label);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Mark any non-default value with bold and add a gear symbol as well.
|
||||||
|
mCommandRows[systemName].get()->setFont(Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD));
|
||||||
|
mCommandRows[systemName].get()->setValue(label + " " + ViewController::GEAR_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
mCommandRows[systemName].get()->setColor(DEFAULT_TEXTCOLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiAlternativeEmulators::selectorWindow(SystemData* system)
|
||||||
|
{
|
||||||
|
auto s = new GuiSettings(mWindow, system->getFullName());
|
||||||
|
|
||||||
|
std::string selectedLabel = system->getAlternativeEmulator();
|
||||||
|
std::string label;
|
||||||
|
|
||||||
|
for (auto entry : system->getSystemEnvData()->mLaunchCommands) {
|
||||||
|
ComponentListRow row;
|
||||||
|
|
||||||
|
if (entry.second == "")
|
||||||
|
label = "<REMOVE INVALID ENTRY>";
|
||||||
|
else
|
||||||
|
label = entry.second;
|
||||||
|
|
||||||
|
std::shared_ptr<TextComponent> labelText = std::make_shared<TextComponent>(
|
||||||
|
mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
||||||
|
|
||||||
|
row.addElement(labelText, true);
|
||||||
|
row.makeAcceptInputHandler([this, s, system, labelText, entry, selectedLabel] {
|
||||||
|
if (entry.second != selectedLabel) {
|
||||||
|
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second)
|
||||||
|
system->setAlternativeEmulator("");
|
||||||
|
else
|
||||||
|
system->setAlternativeEmulator(entry.second);
|
||||||
|
updateGamelist(system, true);
|
||||||
|
updateMenu(
|
||||||
|
system->getName(), labelText->getValue(),
|
||||||
|
(entry.second == system->getSystemEnvData()->mLaunchCommands.front().second));
|
||||||
|
}
|
||||||
|
delete s;
|
||||||
|
});
|
||||||
|
|
||||||
|
// This transparent bracket is only added to generate the correct help prompts.
|
||||||
|
auto bracket = std::make_shared<ImageComponent>(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<float>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuiAlternativeEmulators::input(InputConfig* config, Input input)
|
||||||
|
{
|
||||||
|
if (input.value != 0 && (config->isMappedTo("b", input) || config->isMappedTo("back", input))) {
|
||||||
|
delete this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMenu.input(config, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelpPrompt> GuiAlternativeEmulators::getHelpPrompts()
|
||||||
|
{
|
||||||
|
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
|
||||||
|
prompts.push_back(HelpPrompt("b", "back"));
|
||||||
|
if (mHasSystems)
|
||||||
|
prompts.push_back(HelpPrompt("a", "select"));
|
||||||
|
return prompts;
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpStyle GuiAlternativeEmulators::getHelpStyle()
|
||||||
|
{
|
||||||
|
HelpStyle style = HelpStyle();
|
||||||
|
style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
|
||||||
|
return style;
|
||||||
|
}
|
38
es-app/src/guis/GuiAlternativeEmulators.h
Normal file
38
es-app/src/guis/GuiAlternativeEmulators.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// EmulationStation Desktop Edition
|
||||||
|
// GuiAlternativeEmulators.h
|
||||||
|
//
|
||||||
|
// User interface to select between alternative emulators per system
|
||||||
|
// based on configuration entries in es_systems.xml.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H
|
||||||
|
#define ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H
|
||||||
|
|
||||||
|
#include "GuiComponent.h"
|
||||||
|
#include "guis/GuiSettings.h"
|
||||||
|
|
||||||
|
template <typename T> class OptionListComponent;
|
||||||
|
|
||||||
|
class GuiAlternativeEmulators : public GuiComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GuiAlternativeEmulators(Window* window);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateMenu(const std::string& systemName, const std::string& label, bool defaultEmulator);
|
||||||
|
void selectorWindow(SystemData* system);
|
||||||
|
|
||||||
|
virtual bool input(InputConfig* config, Input input) override;
|
||||||
|
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||||
|
HelpStyle getHelpStyle() override;
|
||||||
|
|
||||||
|
MenuComponent mMenu;
|
||||||
|
bool mHasSystems;
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<TextComponent>> mCommandRows;
|
||||||
|
std::shared_ptr<OptionListComponent<std::string>> mCommandSelection;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H
|
|
@ -20,6 +20,7 @@
|
||||||
#include "components/OptionListComponent.h"
|
#include "components/OptionListComponent.h"
|
||||||
#include "components/SliderComponent.h"
|
#include "components/SliderComponent.h"
|
||||||
#include "components/SwitchComponent.h"
|
#include "components/SwitchComponent.h"
|
||||||
|
#include "guis/GuiAlternativeEmulators.h"
|
||||||
#include "guis/GuiCollectionSystemsOptions.h"
|
#include "guis/GuiCollectionSystemsOptions.h"
|
||||||
#include "guis/GuiComplexTextEditPopup.h"
|
#include "guis/GuiComplexTextEditPopup.h"
|
||||||
#include "guis/GuiDetectDevice.h"
|
#include "guis/GuiDetectDevice.h"
|
||||||
|
@ -772,6 +773,18 @@ void GuiMenu::openOtherOptions()
|
||||||
{
|
{
|
||||||
auto s = new GuiSettings(mWindow, "OTHER SETTINGS");
|
auto s = new GuiSettings(mWindow, "OTHER SETTINGS");
|
||||||
|
|
||||||
|
// Alternative emulators GUI.
|
||||||
|
ComponentListRow alternativeEmulatorsRow;
|
||||||
|
alternativeEmulatorsRow.elements.clear();
|
||||||
|
alternativeEmulatorsRow.addElement(
|
||||||
|
std::make_shared<TextComponent>(mWindow, "ALTERNATIVE EMULATORS",
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
|
||||||
|
true);
|
||||||
|
alternativeEmulatorsRow.addElement(makeArrow(mWindow), false);
|
||||||
|
alternativeEmulatorsRow.makeAcceptInputHandler(
|
||||||
|
std::bind([this] { mWindow->pushGui(new GuiAlternativeEmulators(mWindow)); }));
|
||||||
|
s->addRow(alternativeEmulatorsRow);
|
||||||
|
|
||||||
// Game media directory.
|
// Game media directory.
|
||||||
ComponentListRow rowMediaDir;
|
ComponentListRow rowMediaDir;
|
||||||
auto media_directory = std::make_shared<TextComponent>(mWindow, "GAME MEDIA DIRECTORY",
|
auto media_directory = std::make_shared<TextComponent>(mWindow, "GAME MEDIA DIRECTORY",
|
||||||
|
|
|
@ -198,8 +198,28 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string staticTextString = "Default value from es_systems.xml:";
|
std::string staticTextString = "Default value from es_systems.xml:";
|
||||||
std::string defaultLaunchCommand =
|
std::string defaultLaunchCommand;
|
||||||
scraperParams.system->getSystemEnvData()->mLaunchCommand;
|
|
||||||
|
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()) {
|
||||||
|
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
|
||||||
|
<< "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultLaunchCommand.empty())
|
||||||
|
defaultLaunchCommand =
|
||||||
|
scraperParams.system->getSystemEnvData()->mLaunchCommands.front().first;
|
||||||
|
|
||||||
row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchCommand, ed,
|
row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchCommand, ed,
|
||||||
updateVal, multiLine] {
|
updateVal, multiLine] {
|
||||||
|
|
|
@ -613,6 +613,16 @@ int main(int argc, char* argv[])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if any of the enabled systems has an invalid alternative emulator entry,
|
||||||
|
// 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() == "<INVALID>") {
|
||||||
|
ViewController::get()->invalidAlternativeEmulatorDialog();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't generate controller events while we're loading.
|
// Don't generate controller events while we're loading.
|
||||||
SDL_GameControllerEventState(SDL_DISABLE);
|
SDL_GameControllerEventState(SDL_DISABLE);
|
||||||
|
|
||||||
|
|
|
@ -41,12 +41,14 @@ const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToStrin
|
||||||
const std::string ViewController::TICKMARK_CHAR = Utils::String::wideStringToString(L"\uF14A");
|
const std::string ViewController::TICKMARK_CHAR = Utils::String::wideStringToString(L"\uF14A");
|
||||||
const std::string ViewController::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uF11b");
|
const std::string ViewController::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uF11b");
|
||||||
const std::string ViewController::FILTER_CHAR = Utils::String::wideStringToString(L"\uF0b0");
|
const std::string ViewController::FILTER_CHAR = Utils::String::wideStringToString(L"\uF0b0");
|
||||||
|
const std::string ViewController::GEAR_CHAR = Utils::String::wideStringToString(L"\uF013");
|
||||||
#else
|
#else
|
||||||
const std::string ViewController::FAVORITE_CHAR = "\uF005";
|
const std::string ViewController::FAVORITE_CHAR = "\uF005";
|
||||||
const std::string ViewController::FOLDER_CHAR = "\uF07C";
|
const std::string ViewController::FOLDER_CHAR = "\uF07C";
|
||||||
const std::string ViewController::TICKMARK_CHAR = "\uF14A";
|
const std::string ViewController::TICKMARK_CHAR = "\uF14A";
|
||||||
const std::string ViewController::CONTROLLER_CHAR = "\uF11b";
|
const std::string ViewController::CONTROLLER_CHAR = "\uF11b";
|
||||||
const std::string ViewController::FILTER_CHAR = "\uF0b0";
|
const std::string ViewController::FILTER_CHAR = "\uF0b0";
|
||||||
|
const std::string ViewController::GEAR_CHAR = "\uF013";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ViewController* ViewController::get()
|
ViewController* ViewController::get()
|
||||||
|
@ -193,6 +195,17 @@ void ViewController::noGamesDialog()
|
||||||
mWindow->pushGui(mNoGamesMessageBox);
|
mWindow->pushGui(mNoGamesMessageBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewController::invalidAlternativeEmulatorDialog()
|
||||||
|
{
|
||||||
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
|
||||||
|
"AT LEAST ONE OF YOUR SYSTEMS HAS AN\n"
|
||||||
|
"INVALID ALTERNATIVE EMULATOR CONFIGURED\n"
|
||||||
|
"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"));
|
||||||
|
}
|
||||||
|
|
||||||
void ViewController::goToStart()
|
void ViewController::goToStart()
|
||||||
{
|
{
|
||||||
// If the system view does not exist, then create it. We do this here as it would
|
// If the system view does not exist, then create it. We do this here as it would
|
||||||
|
|
|
@ -38,6 +38,7 @@ public:
|
||||||
// These functions are called from main().
|
// These functions are called from main().
|
||||||
void invalidSystemsFileDialog();
|
void invalidSystemsFileDialog();
|
||||||
void noGamesDialog();
|
void noGamesDialog();
|
||||||
|
void invalidAlternativeEmulatorDialog();
|
||||||
|
|
||||||
// Try to completely populate the GameListView map.
|
// Try to completely populate the GameListView map.
|
||||||
// Caches things so there's no pauses during transitions.
|
// Caches things so there's no pauses during transitions.
|
||||||
|
@ -127,6 +128,7 @@ public:
|
||||||
static const std::string TICKMARK_CHAR;
|
static const std::string TICKMARK_CHAR;
|
||||||
static const std::string CONTROLLER_CHAR;
|
static const std::string CONTROLLER_CHAR;
|
||||||
static const std::string FILTER_CHAR;
|
static const std::string FILTER_CHAR;
|
||||||
|
static const std::string GEAR_CHAR;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ViewController(Window* window);
|
ViewController(Window* window);
|
||||||
|
|
|
@ -223,7 +223,11 @@ private:
|
||||||
|
|
||||||
HelpStyle mHelpStyle;
|
HelpStyle mHelpStyle;
|
||||||
|
|
||||||
void open() { mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName)); }
|
void open()
|
||||||
|
{
|
||||||
|
// Open the list popup.
|
||||||
|
mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName));
|
||||||
|
}
|
||||||
|
|
||||||
void onSelectedChanged()
|
void onSelectedChanged()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue