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
|
||||
|
||||
# GUIs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
|
||||
|
@ -66,6 +67,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
||||
|
||||
# GUIs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp
|
||||
|
|
|
@ -73,7 +73,8 @@ CollectionSystemsManager::CollectionSystemsManager(Window* window)
|
|||
mCollectionEnvData->mStartPath = "";
|
||||
std::vector<std::string> exts;
|
||||
mCollectionEnvData->mSearchExtensions = exts;
|
||||
mCollectionEnvData->mLaunchCommand = "";
|
||||
std::vector<std::pair<std::string, std::string>> commands;
|
||||
mCollectionEnvData->mLaunchCommands = commands;
|
||||
std::vector<PlatformIds::PlatformId> allPlatformIds;
|
||||
allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE);
|
||||
mCollectionEnvData->mPlatformIds = allPlatformIds;
|
||||
|
|
|
@ -756,7 +756,22 @@ void FileData::launchGame(Window* window)
|
|||
command = metadata.get("launchcommand");
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -117,6 +117,30 @@ void parseGamelist(SystemData* system)
|
|||
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();
|
||||
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,
|
||||
// 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 << "\"";
|
||||
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 {
|
||||
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.
|
||||
root = doc.append_child("gameList");
|
||||
}
|
||||
|
@ -312,15 +367,31 @@ void updateGamelist(SystemData* system)
|
|||
}
|
||||
|
||||
// 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).
|
||||
std::string xmlWritePath(system->getGamelistPath(true));
|
||||
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
|
||||
<< (numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlReadPath
|
||||
<< "\"";
|
||||
|
||||
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
|
||||
<< xmlWritePath << "\"";
|
||||
}
|
||||
#if defined(_WIN64)
|
||||
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
|
||||
#else
|
||||
|
|
|
@ -15,6 +15,6 @@ class SystemData;
|
|||
void parseGamelist(SystemData* system);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -417,13 +417,27 @@ bool SystemData::loadConfig()
|
|||
std::string name;
|
||||
std::string fullname;
|
||||
std::string path;
|
||||
std::string cmd;
|
||||
std::string themeFolder;
|
||||
|
||||
name = system.child("name").text().get();
|
||||
fullname = system.child("fullname").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
|
||||
// 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 ""
|
||||
|
@ -461,7 +475,41 @@ bool SystemData::loadConfig()
|
|||
// Convert extensions list from a string into a vector of strings.
|
||||
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
|
||||
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";
|
||||
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
|
||||
<< "\" is missing the fullname, path, "
|
||||
"extension, or command tag, skipping entry";
|
||||
|
@ -526,7 +574,7 @@ bool SystemData::loadConfig()
|
|||
SystemEnvironmentData* envData = new SystemEnvironmentData;
|
||||
envData->mStartPath = path;
|
||||
envData->mSearchExtensions = extensions;
|
||||
envData->mLaunchCommand = cmd;
|
||||
envData->mLaunchCommands = commands;
|
||||
envData->mPlatformIds = platformIds;
|
||||
|
||||
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
|
||||
|
@ -679,7 +727,7 @@ bool SystemData::createSystemDirectories()
|
|||
std::string fullname;
|
||||
std::string path;
|
||||
std::string extensions;
|
||||
std::string command;
|
||||
std::vector<std::string> commands;
|
||||
std::string platform;
|
||||
std::string themeFolder;
|
||||
const std::string systemInfoFileName = "/systeminfo.txt";
|
||||
|
@ -690,7 +738,10 @@ bool SystemData::createSystemDirectories()
|
|||
fullname = system.child("fullname").text().get();
|
||||
path = system.child("path").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());
|
||||
themeFolder = system.child("theme").text().as_string(name.c_str());
|
||||
|
||||
|
@ -757,7 +808,16 @@ bool SystemData::createSystemDirectories()
|
|||
systemInfoFile << "Supported file extensions:" << std::endl;
|
||||
systemInfoFile << extensions << std::endl << 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 << std::endl << std::endl;
|
||||
systemInfoFile << "Theme folder:" << std::endl;
|
||||
|
|
|
@ -27,7 +27,7 @@ class ThemeData;
|
|||
struct SystemEnvironmentData {
|
||||
std::string mStartPath;
|
||||
std::vector<std::string> mSearchExtensions;
|
||||
std::string mLaunchCommand;
|
||||
std::vector<std::pair<std::string, std::string>> mLaunchCommands;
|
||||
std::vector<PlatformIds::PlatformId> mPlatformIds;
|
||||
};
|
||||
|
||||
|
@ -97,6 +97,9 @@ public:
|
|||
bool getScrapeFlag() { return mScrapeFlag; }
|
||||
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
|
||||
|
||||
std::string getAlternativeEmulator() { return mAlternativeEmulator; }
|
||||
void setAlternativeEmulator(const std::string& command) { mAlternativeEmulator = command; }
|
||||
|
||||
static void deleteSystems();
|
||||
// Loads the systems configuration file at getConfigPath() and creates the systems.
|
||||
static bool loadConfig();
|
||||
|
@ -153,6 +156,7 @@ private:
|
|||
std::string mName;
|
||||
std::string mFullName;
|
||||
SystemEnvironmentData* mEnvData;
|
||||
std::string mAlternativeEmulator;
|
||||
std::string mThemeFolder;
|
||||
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/SliderComponent.h"
|
||||
#include "components/SwitchComponent.h"
|
||||
#include "guis/GuiAlternativeEmulators.h"
|
||||
#include "guis/GuiCollectionSystemsOptions.h"
|
||||
#include "guis/GuiComplexTextEditPopup.h"
|
||||
#include "guis/GuiDetectDevice.h"
|
||||
|
@ -772,6 +773,18 @@ void GuiMenu::openOtherOptions()
|
|||
{
|
||||
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.
|
||||
ComponentListRow rowMediaDir;
|
||||
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 defaultLaunchCommand =
|
||||
scraperParams.system->getSystemEnvData()->mLaunchCommand;
|
||||
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()) {
|
||||
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,
|
||||
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.
|
||||
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::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uF11b");
|
||||
const std::string ViewController::FILTER_CHAR = Utils::String::wideStringToString(L"\uF0b0");
|
||||
const std::string ViewController::GEAR_CHAR = Utils::String::wideStringToString(L"\uF013");
|
||||
#else
|
||||
const std::string ViewController::FAVORITE_CHAR = "\uF005";
|
||||
const std::string ViewController::FOLDER_CHAR = "\uF07C";
|
||||
const std::string ViewController::TICKMARK_CHAR = "\uF14A";
|
||||
const std::string ViewController::CONTROLLER_CHAR = "\uF11b";
|
||||
const std::string ViewController::FILTER_CHAR = "\uF0b0";
|
||||
const std::string ViewController::GEAR_CHAR = "\uF013";
|
||||
#endif
|
||||
|
||||
ViewController* ViewController::get()
|
||||
|
@ -193,6 +195,17 @@ void ViewController::noGamesDialog()
|
|||
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()
|
||||
{
|
||||
// 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().
|
||||
void invalidSystemsFileDialog();
|
||||
void noGamesDialog();
|
||||
void invalidAlternativeEmulatorDialog();
|
||||
|
||||
// Try to completely populate the GameListView map.
|
||||
// Caches things so there's no pauses during transitions.
|
||||
|
@ -127,6 +128,7 @@ public:
|
|||
static const std::string TICKMARK_CHAR;
|
||||
static const std::string CONTROLLER_CHAR;
|
||||
static const std::string FILTER_CHAR;
|
||||
static const std::string GEAR_CHAR;
|
||||
|
||||
private:
|
||||
ViewController(Window* window);
|
||||
|
|
|
@ -223,7 +223,11 @@ private:
|
|||
|
||||
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()
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue