ES-DE/es-app/src/guis/GuiAlternativeEmulators.cpp
2022-09-03 12:44:49 +02:00

262 lines
9.8 KiB
C++

// 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 "GamelistFileParser.h"
#include "SystemData.h"
#include "views/ViewController.h"
GuiAlternativeEmulators::GuiAlternativeEmulators()
: mMenu {"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};
std::vector<SystemData*> sortedSystems {SystemData::sSystemVector};
// Sort systems by short name.
std::sort(std::begin(sortedSystems), std::end(sortedSystems), [](SystemData* a, SystemData* b) {
return Utils::String::toUpper(a->getName()) < Utils::String::toUpper(b->getName());
});
for (auto it = sortedSystems.cbegin(); it != sortedSystems.cend(); ++it) {
// Only include systems that have at least two command entries, unless the system
// has an invalid entry.
if ((*it)->getAlternativeEmulator().substr(0, 9) != "<INVALID>" &&
(*it)->getSystemEnvData()->mLaunchCommands.size() < 2)
continue;
ComponentListRow row;
std::string name {(*it)->getName()};
std::shared_ptr<TextComponent> systemText {
std::make_shared<TextComponent>(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 = ViewController::EXCLAMATION_CHAR + " INVALID ENTRY";
invalidEntry = true;
}
std::shared_ptr<TextComponent> labelText;
if (label == (*it)->getSystemEnvData()->mLaunchCommands.front().second) {
labelText = std::make_shared<TextComponent>(
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>(
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(mMenu.getSize().x - systemSizeX -
20.0f * Renderer::getScreenHeightModifier(),
systemText->getSize().y);
row.addElement(labelText, false);
SystemData* systemEntry {
*std::find(SystemData::sSystemVector.cbegin(), SystemData::sSystemVector.cend(), *it)};
row.makeAcceptInputHandler([this, systemEntry, labelText] {
if (labelText->getValue() == ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY")
return;
selectorWindow(systemEntry);
});
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>(
ViewController::EXCLAMATION_CHAR + " NO ALTERNATIVE EMULATORS DEFINED",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER)};
row.addElement(systemText, true);
mMenu.addRow(row);
}
setSize(mMenu.getSize());
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, Renderer::getScreenHeight() * 0.13f);
}
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(system->getFullName());
std::string selectedLabel {system->getAlternativeEmulator()};
std::string label;
for (auto entry : system->getSystemEnvData()->mLaunchCommands) {
ComponentListRow row;
if (entry.second == "")
label = ViewController::CROSSEDCIRCLE_CHAR + " CLEAR INVALID ENTRY";
else
label = entry.second;
std::shared_ptr<TextComponent> labelText = std::make_shared<TextComponent>(
label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_LEFT);
labelText->setSelectable(true);
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) {
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second)
system->setAlternativeEmulator("");
else
system->setAlternativeEmulator(entry.second);
GamelistFileParser::updateGamelist(system, true);
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) {
if (system->getSystemEnvData()->mLaunchCommands.front().second == "") {
updateMenu(system->getName(),
ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY",
(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;
});
// Select the row that corresponds to the selected label.
if (selectedLabel == label)
s->addRow(row, true);
else
s->addRow(row, false);
}
// Set a maximum 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.77f * aspectValue, 0.50f, 0.92f)};
float maxWidth {Renderer::getScreenWidth() * maxWidthModifier};
// Set the width of the selector window to the menu width, unless the system full name is
// too large to fit. If so, allow the size to be exceeded up to the maximum size calculated
// above.
float systemTextWidth {
Font::get(FONT_SIZE_LARGE)->sizeText(Utils::String::toUpper(system->getFullName())).x *
1.15f};
float width {0.0f};
float menuWidth {mMenu.getSize().x};
if (systemTextWidth <= menuWidth)
width = menuWidth;
else if (systemTextWidth > maxWidth)
width = maxWidth;
else
width = systemTextWidth;
s->setMenuSize(glm::vec2 {width, s->getMenuSize().y});
auto menuSize = s->getMenuSize();
auto menuPos = s->getMenuPosition();
s->setMenuPosition(glm::vec3 {(s->getSize().x - menuSize.x) / 2.0f, menuPos.y, menuPos.z});
// Hack to properly update the window and set the scroll indicators. Why this is required
// is currently a mystery.
auto list = s->getMenu().getList();
list->update(1);
int cursor {list->getCursorId()};
list->setCursor(list->getFirst());
list->moveCursor(cursor);
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;
}