// SPDX-License-Identifier: MIT // // ES-DE // GuiMenu.cpp // // Main menu. // Some submenus are covered in separate source files. // #include "guis/GuiMenu.h" #if defined(_WIN64) // Why this is needed here is anyone's guess but without it the compilation fails. #include #endif #include "ApplicationVersion.h" #include "CollectionSystemsManager.h" #include "FileFilterIndex.h" #include "FileSorts.h" #include "Scripting.h" #include "SystemData.h" #include "UIModeController.h" #include "VolumeControl.h" #include "components/OptionListComponent.h" #include "components/SliderComponent.h" #include "components/SwitchComponent.h" #include "guis/GuiAlternativeEmulators.h" #include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiDetectDevice.h" #include "guis/GuiMediaViewerOptions.h" #include "guis/GuiMsgBox.h" #include "guis/GuiOrphanedDataCleanup.h" #include "guis/GuiScraperMenu.h" #include "guis/GuiScreensaverOptions.h" #include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditPopup.h" #include "guis/GuiThemeDownloader.h" #include "utils/PlatformUtil.h" #if defined(__ANDROID__) #include "InputOverlay.h" #include "utils/PlatformUtilAndroid.h" #endif #include #include GuiMenu::GuiMenu() : mRenderer {Renderer::getInstance()} , mMenu {"MAIN MENU"} , mThemeDownloaderReloadCounter {0} { const bool isFullUI {UIModeController::getInstance()->isUIModeFull()}; if (isFullUI) addEntry("SCRAPER", mMenuColorPrimary, true, [this] { openScraperOptions(); }); if (isFullUI) addEntry("RETRODECK CONFIGURATOR", mMenuColorPrimary, false, [this] { openRetroDeckConfigurator(); }); if (isFullUI) addEntry("ES-DE CONFIGURATIONS", mMenuColorPrimary, true, [this] { openESDEConfiguration(); }); if (isFullUI) addEntry("UTILITIES", mMenuColorPrimary, true, [this] { openUtilities(); }); if (!Settings::getInstance()->getBool("ForceKiosk") && Settings::getInstance()->getString("UIMode") != "kiosk") { #if defined(__APPLE__) addEntry("QUIT RETRODECK", mMenuColorPrimary, false, [this] { openQuitMenu(); }); #elif defined(__ANDROID__) if (!AndroidVariables::sIsHomeApp) addEntry("QUIT RETRODECK", mMenuColorPrimary, false, [this] { openQuitMenu(); }); #else if (Settings::getInstance()->getBool("ShowQuitMenu")) addEntry("QUIT", mMenuColorPrimary, true, [this] { openQuitMenu(); }); else addEntry("QUIT RETRODECK", mMenuColorPrimary, false, [this] { openQuitMenu(); }); #endif } addChild(&mMenu); addVersionInfo(); setSize(mMenu.getSize()); setPosition((mRenderer->getScreenWidth() - mSize.x) / 2.0f, std::round(mRenderer->getScreenHeight() * 0.13f)); } GuiMenu::~GuiMenu() { if (ViewController::getInstance()->getState().viewing != ViewController::ViewMode::NOTHING) { // This is required for the situation where scrolling started just before the menu // was openened. Without this, the scrolling would run until manually stopped after // the menu has been closed. ViewController::getInstance()->stopScrolling(); ViewController::getInstance()->startViewVideos(); } } void GuiMenu::openScraperOptions() { // Open the scraper menu. mWindow->pushGui(new GuiScraperMenu("SCRAPER")); } void GuiMenu::openUIOptions() { auto s = new GuiSettings("UI SETTINGS"); // Theme options section. std::map themes { ThemeData::getThemes()}; std::map::const_iterator selectedTheme; auto theme = std::make_shared>(getHelpStyle(), "THEME", false); ComponentListRow themeDownloaderInputRow; themeDownloaderInputRow.elements.clear(); themeDownloaderInputRow.addElement(std::make_shared("THEME DOWNLOADER", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); themeDownloaderInputRow.addElement(mMenu.makeArrow(), false); themeDownloaderInputRow.makeAcceptInputHandler( std::bind(&GuiMenu::openThemeDownloader, this, s)); s->addRow(themeDownloaderInputRow); // Theme. if (!themes.empty()) { selectedTheme = themes.find(Settings::getInstance()->getString("Theme")); if (selectedTheme == themes.cend()) selectedTheme = themes.cbegin(); std::vector>> themesSorted; std::string sortName; for (auto& theme : themes) { if (theme.second.capabilities.themeName != "") sortName = theme.second.capabilities.themeName; else sortName = theme.first; themesSorted.emplace_back(std::make_pair(Utils::String::toUpper(sortName), std::make_pair(theme.first, theme.second))); } std::sort(themesSorted.begin(), themesSorted.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); for (auto it = themesSorted.cbegin(); it != themesSorted.cend(); ++it) { // If required, abbreviate the theme name so it doesn't overlap the setting name. const float maxNameLength {mSize.x * 0.62f}; std::string themeName {(*it).first}; theme->add(themeName, it->second.first, (*it).second.first == selectedTheme->first, maxNameLength); } s->addWithLabel("THEME", theme); s->addSaveFunc([this, theme, s] { if (theme->getSelected() != Settings::getInstance()->getString("Theme")) { Scripting::fireEvent("theme-changed", theme->getSelected(), Settings::getInstance()->getString("Theme")); // Handle the situation where the previously selected theme has been deleted // using the theme downloader. In this case attempt to fall back to linear-es-de // and if this theme doesn't exist then select the first available one. auto themes = ThemeData::getThemes(); if (themes.find(theme->getSelected()) == themes.end()) { if (themes.find("linear-es-de") != themes.end()) Settings::getInstance()->setString("Theme", "linear-es-de"); else Settings::getInstance()->setString("Theme", themes.begin()->first); } else { Settings::getInstance()->setString("Theme", theme->getSelected()); } mWindow->setChangedTheme(); // This is required so that the custom collection system does not disappear // if the user is editing a custom collection when switching themes. if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); s->setNeedsSaving(); s->setNeedsReloading(); s->setNeedsGoToStart(); s->setNeedsCollectionsUpdate(); s->setInvalidateCachedBackground(); } }); } // Theme variants. auto themeVariant = std::make_shared>(getHelpStyle(), "THEME VARIANT", false); s->addWithLabel("THEME VARIANT", themeVariant); s->addSaveFunc([themeVariant, s] { if (themeVariant->getSelected() != Settings::getInstance()->getString("ThemeVariant")) { Settings::getInstance()->setString("ThemeVariant", themeVariant->getSelected()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); auto themeVariantsFunc = [=](const std::string& selectedTheme, const std::string& selectedVariant) { std::map::const_iterator currentSet {themes.find(selectedTheme)}; if (currentSet == themes.cend()) return; // We need to recreate the OptionListComponent entries. themeVariant->clearEntries(); int selectableVariants {0}; for (auto& variant : currentSet->second.capabilities.variants) { if (variant.selectable) ++selectableVariants; } if (selectableVariants > 0) { for (auto& variant : currentSet->second.capabilities.variants) { if (variant.selectable) { // If required, abbreviate the variant name so it doesn't overlap the // setting name. const float maxNameLength {mSize.x * 0.62f}; themeVariant->add(variant.label, variant.name, variant.name == selectedVariant, maxNameLength); } } if (themeVariant->getSelectedObjects().size() == 0) themeVariant->selectEntry(0); } else { themeVariant->add("None defined", "none", true); themeVariant->setEnabled(false); themeVariant->setOpacity(DISABLED_OPACITY); themeVariant->getParent() ->getChild(themeVariant->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } }; themeVariantsFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeVariant")); // Theme color schemes. auto themeColorScheme = std::make_shared>( getHelpStyle(), "THEME COLOR SCHEME", false); s->addWithLabel("THEME COLOR SCHEME", themeColorScheme); s->addSaveFunc([themeColorScheme, s] { if (themeColorScheme->getSelected() != Settings::getInstance()->getString("ThemeColorScheme")) { Settings::getInstance()->setString("ThemeColorScheme", themeColorScheme->getSelected()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); auto themeColorSchemesFunc = [=](const std::string& selectedTheme, const std::string& selectedColorScheme) { std::map::const_iterator currentSet {themes.find(selectedTheme)}; if (currentSet == themes.cend()) return; // We need to recreate the OptionListComponent entries. themeColorScheme->clearEntries(); if (currentSet->second.capabilities.colorSchemes.size() > 0) { for (auto& colorScheme : currentSet->second.capabilities.colorSchemes) { // If required, abbreviate the color scheme name so it doesn't overlap the // setting name. const float maxNameLength {mSize.x * 0.52f}; themeColorScheme->add(colorScheme.label, colorScheme.name, colorScheme.name == selectedColorScheme, maxNameLength); } if (themeColorScheme->getSelectedObjects().size() == 0) themeColorScheme->selectEntry(0); } else { themeColorScheme->add("None defined", "none", true); themeColorScheme->setEnabled(false); themeColorScheme->setOpacity(DISABLED_OPACITY); themeColorScheme->getParent() ->getChild(themeColorScheme->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } }; themeColorSchemesFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeColorScheme")); // Theme font sizes. auto themeFontSize = std::make_shared>( getHelpStyle(), "THEME FONT SIZE", false); s->addWithLabel("THEME FONT SIZE", themeFontSize); s->addSaveFunc([themeFontSize, s] { if (themeFontSize->getSelected() != Settings::getInstance()->getString("ThemeFontSize")) { Settings::getInstance()->setString("ThemeFontSize", themeFontSize->getSelected()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); auto themeFontSizeFunc = [=](const std::string& selectedTheme, const std::string& selectedFontSize) { std::map::const_iterator currentSet {themes.find(selectedTheme)}; if (currentSet == themes.cend()) return; // We need to recreate the OptionListComponent entries. themeFontSize->clearEntries(); if (currentSet->second.capabilities.fontSizes.size() > 0) { for (auto& fontSize : currentSet->second.capabilities.fontSizes) themeFontSize->add(ThemeData::getFontSizeLabel(fontSize), fontSize, fontSize == selectedFontSize); if (themeFontSize->getSelectedObjects().size() == 0) themeFontSize->selectEntry(0); } else { themeFontSize->add("None defined", "none", true); themeFontSize->setEnabled(false); themeFontSize->setOpacity(DISABLED_OPACITY); themeFontSize->getParent() ->getChild(themeFontSize->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } }; themeFontSizeFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeFontSize")); // Theme aspect ratios. auto themeAspectRatio = std::make_shared>( getHelpStyle(), "THEME ASPECT RATIO", false); s->addWithLabel("THEME ASPECT RATIO", themeAspectRatio); s->addSaveFunc([themeAspectRatio, s] { if (themeAspectRatio->getSelected() != Settings::getInstance()->getString("ThemeAspectRatio")) { Settings::getInstance()->setString("ThemeAspectRatio", themeAspectRatio->getSelected()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); auto themeAspectRatiosFunc = [=](const std::string& selectedTheme, const std::string& selectedAspectRatio) { std::map::const_iterator currentSet {themes.find(selectedTheme)}; if (currentSet == themes.cend()) return; // We need to recreate the OptionListComponent entries. themeAspectRatio->clearEntries(); if (currentSet->second.capabilities.aspectRatios.size() > 0) { for (auto& aspectRatio : currentSet->second.capabilities.aspectRatios) themeAspectRatio->add(ThemeData::getAspectRatioLabel(aspectRatio), aspectRatio, aspectRatio == selectedAspectRatio); if (themeAspectRatio->getSelectedObjects().size() == 0) themeAspectRatio->selectEntry(0); } else { themeAspectRatio->add("None defined", "none", true); themeAspectRatio->setEnabled(false); themeAspectRatio->setOpacity(DISABLED_OPACITY); themeAspectRatio->getParent() ->getChild(themeAspectRatio->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } }; themeAspectRatiosFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeAspectRatio")); // Theme transitions. auto themeTransitions = std::make_shared>( getHelpStyle(), "THEME TRANSITIONS", false); std::string selectedThemeTransitions {Settings::getInstance()->getString("ThemeTransitions")}; themeTransitions->add("AUTOMATIC", "automatic", selectedThemeTransitions == "automatic"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set theme transitions to "automatic" in this case. if (themeTransitions->getSelectedObjects().size() == 0) themeTransitions->selectEntry(0); s->addWithLabel("THEME TRANSITIONS", themeTransitions); s->addSaveFunc([themeTransitions, s] { if (themeTransitions->getSelected() != Settings::getInstance()->getString("ThemeTransitions")) { Settings::getInstance()->setString("ThemeTransitions", themeTransitions->getSelected()); ThemeData::setThemeTransitions(); s->setNeedsSaving(); } }); auto themeTransitionsFunc = [=](const std::string& selectedTheme, const std::string& selectedThemeTransitions) { std::map::const_iterator currentSet {themes.find(selectedTheme)}; if (currentSet == themes.cend()) return; // We need to recreate the OptionListComponent entries. themeTransitions->clearEntries(); themeTransitions->add("AUTOMATIC", "automatic", "automatic" == selectedThemeTransitions); if (currentSet->second.capabilities.transitions.size() == 1 && currentSet->second.capabilities.transitions.front().selectable) { std::string label; if (currentSet->second.capabilities.transitions.front().label == "") label = "THEME PROFILE"; else label = currentSet->second.capabilities.transitions.front().label; const std::string transitions { currentSet->second.capabilities.transitions.front().name}; themeTransitions->add(label, transitions, transitions == selectedThemeTransitions); } else { for (size_t i {0}; i < currentSet->second.capabilities.transitions.size(); ++i) { if (!currentSet->second.capabilities.transitions[i].selectable) continue; std::string label; if (currentSet->second.capabilities.transitions[i].label == "") label = "THEME PROFILE " + std::to_string(i + 1); else label = currentSet->second.capabilities.transitions[i].label; const std::string transitions {currentSet->second.capabilities.transitions[i].name}; themeTransitions->add(label, transitions, transitions == selectedThemeTransitions); } } if (std::find(currentSet->second.capabilities.suppressedTransitionProfiles.cbegin(), currentSet->second.capabilities.suppressedTransitionProfiles.cend(), "builtin-instant") == currentSet->second.capabilities.suppressedTransitionProfiles.cend()) { themeTransitions->add("INSTANT (BUILT-IN)", "builtin-instant", "builtin-instant" == selectedThemeTransitions); } if (std::find(currentSet->second.capabilities.suppressedTransitionProfiles.cbegin(), currentSet->second.capabilities.suppressedTransitionProfiles.cend(), "builtin-slide") == currentSet->second.capabilities.suppressedTransitionProfiles.cend()) { themeTransitions->add("SLIDE (BUILT-IN)", "builtin-slide", "builtin-slide" == selectedThemeTransitions); } if (std::find(currentSet->second.capabilities.suppressedTransitionProfiles.cbegin(), currentSet->second.capabilities.suppressedTransitionProfiles.cend(), "builtin-fade") == currentSet->second.capabilities.suppressedTransitionProfiles.cend()) { themeTransitions->add("FADE (BUILT-IN)", "builtin-fade", "builtin-fade" == selectedThemeTransitions); } if (themeTransitions->getSelectedObjects().size() == 0) themeTransitions->selectEntry(0); if (themeTransitions->getNumEntries() == 1) { themeTransitions->setEnabled(false); themeTransitions->setOpacity(DISABLED_OPACITY); themeTransitions->getParent() ->getChild(themeTransitions->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } else { themeTransitions->setEnabled(true); themeTransitions->setOpacity(1.0f); themeTransitions->getParent() ->getChild(themeTransitions->getChildIndex() - 1) ->setOpacity(1.0f); } }; themeTransitionsFunc(Settings::getInstance()->getString("Theme"), Settings::getInstance()->getString("ThemeTransitions")); // Quick system select (navigate between systems in the gamelist view). auto quickSystemSelect = std::make_shared>( getHelpStyle(), "QUICK SYSTEM SELECT", false); std::string selectedQuickSelect {Settings::getInstance()->getString("QuickSystemSelect")}; quickSystemSelect->add("LEFT/RIGHT OR SHOULDERS", "leftrightshoulders", selectedQuickSelect == "leftrightshoulders"); quickSystemSelect->add("LEFT/RIGHT OR TRIGGERS", "leftrighttriggers", selectedQuickSelect == "leftrighttriggers"); quickSystemSelect->add("SHOULDERS", "shoulders", selectedQuickSelect == "shoulders"); quickSystemSelect->add("TRIGGERS", "triggers", selectedQuickSelect == "triggers"); quickSystemSelect->add("LEFT/RIGHT", "leftright", selectedQuickSelect == "leftright"); quickSystemSelect->add("DISABLED", "disabled", selectedQuickSelect == "disabled"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the quick system select to "leftrightshoulders" in this case. if (quickSystemSelect->getSelectedObjects().size() == 0) quickSystemSelect->selectEntry(0); s->addWithLabel("QUICK SYSTEM SELECT", quickSystemSelect); s->addSaveFunc([quickSystemSelect, s] { if (quickSystemSelect->getSelected() != Settings::getInstance()->getString("QuickSystemSelect")) { Settings::getInstance()->setString("QuickSystemSelect", quickSystemSelect->getSelected()); s->setNeedsSaving(); } }); // Optionally start in selected system/gamelist. auto startupSystem = std::make_shared>( getHelpStyle(), "GAMELIST ON STARTUP", false); startupSystem->add("NONE", "", Settings::getInstance()->getString("StartupSystem") == ""); for (auto it = SystemData::sSystemVector.cbegin(); // Line break. it != SystemData::sSystemVector.cend(); ++it) { // If required, abbreviate the system name so it doesn't overlap the setting name. float maxNameLength {mSize.x * 0.51f}; startupSystem->add((*it)->getFullName(), (*it)->getName(), Settings::getInstance()->getString("StartupSystem") == (*it)->getName(), maxNameLength); } // This can probably not happen but as an extra precaution select the "NONE" entry if no // entry is selected. if (startupSystem->getSelectedObjects().size() == 0) startupSystem->selectEntry(0); s->addWithLabel("GAMELIST ON STARTUP", startupSystem); s->addSaveFunc([startupSystem, s] { if (startupSystem->getSelected() != Settings::getInstance()->getString("StartupSystem")) { Settings::getInstance()->setString("StartupSystem", startupSystem->getSelected()); s->setNeedsSaving(); } }); // Systems sorting. auto systemsSorting = std::make_shared>( getHelpStyle(), "SYSTEMS SORTING", false); std::string selectedSystemsSorting {Settings::getInstance()->getString("SystemsSorting")}; systemsSorting->add("FULL NAMES OR CUSTOM", "default", selectedSystemsSorting == "default"); systemsSorting->add("RELEASE YEAR", "year", selectedSystemsSorting == "year"); systemsSorting->add("MANUFACTURER, RELEASE YEAR", "manufacturer_year", selectedSystemsSorting == "manufacturer_year"); systemsSorting->add("HW TYPE, RELEASE YEAR", "hwtype_year", selectedSystemsSorting == "hwtype_year"); systemsSorting->add("MANUFACTURER, HW TYPE, REL. YEAR", "manufacturer_hwtype_year", selectedSystemsSorting == "manufacturer_hwtype_year"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the systems sorting to "default" in this case. if (systemsSorting->getSelectedObjects().size() == 0) systemsSorting->selectEntry(0); s->addWithLabel("SYSTEMS SORTING", systemsSorting); s->addSaveFunc([this, systemsSorting, s] { if (systemsSorting->getSelected() != Settings::getInstance()->getString("SystemsSorting")) { Settings::getInstance()->setString("SystemsSorting", systemsSorting->getSelected()); s->setNeedsSaving(); if (mThemeDownloaderReloadCounter == 0) s->setNeedsCloseMenu([this] { delete this; }); else ++mThemeDownloaderReloadCounter; s->setNeedsRescanROMDirectory(); } }); // Default gamelist sort order. std::string sortOrder; auto defaultSortOrder = std::make_shared>( getHelpStyle(), "DEFAULT SORT ORDER", false); // Exclude the System sort options. unsigned int numSortTypes {static_cast(FileSorts::SortTypes.size() - 2)}; for (unsigned int i {0}; i < numSortTypes; ++i) { if (FileSorts::SortTypes[i].description == Settings::getInstance()->getString("DefaultSortOrder")) { sortOrder = FileSorts::SortTypes[i].description; break; } } // If an invalid sort order was defined in es_settings.xml, then apply the default // sort order "name, ascending". if (sortOrder == "") { sortOrder = Settings::getInstance()->getDefaultString("DefaultSortOrder"); Settings::getInstance()->setString("DefaultSortOrder", sortOrder); s->setNeedsSaving(); } for (unsigned int i {0}; i < numSortTypes; ++i) { const FileData::SortType& sort {FileSorts::SortTypes[i]}; if (sort.description == sortOrder) defaultSortOrder->add(sort.description, &sort, true); else defaultSortOrder->add(sort.description, &sort, false); } s->addWithLabel("GAMES DEFAULT SORT ORDER", defaultSortOrder); s->addSaveFunc([defaultSortOrder, sortOrder, s] { std::string selectedSortOrder {defaultSortOrder.get()->getSelected()->description}; if (selectedSortOrder != sortOrder) { Settings::getInstance()->setString("DefaultSortOrder", selectedSortOrder); s->setNeedsSaving(); s->setNeedsSorting(); s->setNeedsSortingCollections(); s->setInvalidateCachedBackground(); } }); // Menu color scheme. auto menuColorScheme = std::make_shared>( getHelpStyle(), "MENU COLOR SCHEME", false); const std::string selectedMenuColor {Settings::getInstance()->getString("MenuColorScheme")}; menuColorScheme->add("DARK", "dark", selectedMenuColor == "dark"); menuColorScheme->add("LIGHT", "light", selectedMenuColor == "light"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the menu color scheme to "dark" in this case. if (menuColorScheme->getSelectedObjects().size() == 0) menuColorScheme->selectEntry(0); s->addWithLabel("MENU COLOR SCHEME", menuColorScheme); s->addSaveFunc([this, menuColorScheme, s] { if (menuColorScheme->getSelected() != Settings::getInstance()->getString("MenuColorScheme")) { Settings::getInstance()->setString("MenuColorScheme", menuColorScheme->getSelected()); ViewController::getInstance()->setMenuColors(); s->setNeedsSaving(); if (mThemeDownloaderReloadCounter == 0) s->setNeedsCloseMenu([this] { delete this; }); else ++mThemeDownloaderReloadCounter; } }); // Open menu effect. auto menuOpeningEffect = std::make_shared>( getHelpStyle(), "MENU OPENING EFFECT", false); std::string selectedMenuEffect {Settings::getInstance()->getString("MenuOpeningEffect")}; menuOpeningEffect->add("SCALE-UP", "scale-up", selectedMenuEffect == "scale-up"); menuOpeningEffect->add("NONE", "none", selectedMenuEffect == "none"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the opening effect to "scale-up" in this case. if (menuOpeningEffect->getSelectedObjects().size() == 0) menuOpeningEffect->selectEntry(0); s->addWithLabel("MENU OPENING EFFECT", menuOpeningEffect); s->addSaveFunc([menuOpeningEffect, s] { if (menuOpeningEffect->getSelected() != Settings::getInstance()->getString("MenuOpeningEffect")) { Settings::getInstance()->setString("MenuOpeningEffect", menuOpeningEffect->getSelected()); s->setNeedsSaving(); } }); // Launch screen duration. auto launchScreenDuration = std::make_shared>( getHelpStyle(), "LAUNCH SCREEN DURATION", false); std::string selectedDuration {Settings::getInstance()->getString("LaunchScreenDuration")}; launchScreenDuration->add("NORMAL", "normal", selectedDuration == "normal"); launchScreenDuration->add("BRIEF", "brief", selectedDuration == "brief"); launchScreenDuration->add("LONG", "long", selectedDuration == "long"); launchScreenDuration->add("DISABLED", "disabled", selectedDuration == "disabled"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the duration to "normal" in this case. if (launchScreenDuration->getSelectedObjects().size() == 0) launchScreenDuration->selectEntry(0); s->addWithLabel("LAUNCH SCREEN DURATION", launchScreenDuration); s->addSaveFunc([launchScreenDuration, s] { if (launchScreenDuration->getSelected() != Settings::getInstance()->getString("LaunchScreenDuration")) { Settings::getInstance()->setString("LaunchScreenDuration", launchScreenDuration->getSelected()); s->setNeedsSaving(); } }); // UI mode. auto uiMode = std::make_shared>(getHelpStyle(), "UI MODE", false); std::vector uiModes; uiModes.push_back("full"); uiModes.push_back("kiosk"); uiModes.push_back("kid"); std::string setMode; if (Settings::getInstance()->getBool("ForceKiosk")) setMode = "kiosk"; else if (Settings::getInstance()->getBool("ForceKid")) setMode = "kid"; else setMode = Settings::getInstance()->getString("UIMode"); for (auto it = uiModes.cbegin(); it != uiModes.cend(); ++it) uiMode->add(*it, *it, setMode == *it); s->addWithLabel("UI MODE", uiMode); s->addSaveFunc([uiMode, this, s] { std::string selectedMode {uiMode->getSelected()}; // If any of the force flags are set, then always apply and save the setting. if (selectedMode == Settings::getInstance()->getString("UIMode") && !Settings::getInstance()->getBool("ForceFull") && !Settings::getInstance()->getBool("ForceKiosk") && !Settings::getInstance()->getBool("ForceKid")) { return; } else if (selectedMode != "full") { std::string msg {"YOU ARE CHANGING THE UI TO THE RESTRICTED MODE\n'" + Utils::String::toUpper(selectedMode) + "'\n"}; if (selectedMode == "kiosk") { msg.append("THIS WILL HIDE MOST MENU OPTIONS TO PREVENT\n"); msg.append("CHANGES TO THE SYSTEM\n"); } else { msg.append("THIS WILL LIMIT THE AVAILABLE GAMES TO THE ONES\n"); msg.append("FLAGGED SUITABLE FOR CHILDREN\n"); } msg.append("TO UNLOCK AND RETURN TO THE FULL UI, ENTER THIS CODE: \n") .append(UIModeController::getInstance()->getFormattedPassKeyStr()) .append("\n\n") .append("DO YOU WANT TO PROCEED?"); mWindow->pushGui(new GuiMsgBox( this->getHelpStyle(), msg, "YES", [this, selectedMode] { LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '" << selectedMode << "'."; Settings::getInstance()->setString("UIMode", selectedMode); Settings::getInstance()->setBool("ForceFull", false); Settings::getInstance()->setBool("ForceKiosk", false); Settings::getInstance()->setBool("ForceKid", false); Settings::getInstance()->saveFile(); if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); UIModeController::getInstance()->setCurrentUIMode(selectedMode); for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it) { if ((*it)->getThemeFolder() == "custom-collections") { for (FileData* customSystem : (*it)->getRootFolder()->getChildrenListToDisplay()) customSystem->getSystem()->getIndex()->resetFilters(); } (*it)->sortSystem(); (*it)->getIndex()->resetFilters(); } ViewController::getInstance()->reloadAll(); ViewController::getInstance()->goToSystem(SystemData::sSystemVector.front(), false); mWindow->invalidateCachedBackground(); }, "NO", nullptr, "", nullptr, nullptr, true)); } else { LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '" << selectedMode << "'."; Settings::getInstance()->setString("UIMode", uiMode->getSelected()); Settings::getInstance()->setBool("ForceFull", false); Settings::getInstance()->setBool("ForceKiosk", false); Settings::getInstance()->setBool("ForceKid", false); UIModeController::getInstance()->setCurrentUIMode("full"); s->setNeedsSaving(); s->setNeedsSorting(); s->setNeedsSortingCollections(); s->setNeedsResetFilters(); s->setNeedsReloading(); s->setNeedsGoToSystem(SystemData::sSystemVector.front()); s->setInvalidateCachedBackground(); } }); // Random entry button. auto randomEntryButton = std::make_shared>( getHelpStyle(), "RANDOM ENTRY BUTTON", false); const std::string selectedRandomEntryButton { Settings::getInstance()->getString("RandomEntryButton")}; randomEntryButton->add("GAMES ONLY", "games", selectedRandomEntryButton == "games"); randomEntryButton->add("GAMES AND SYSTEMS", "gamessystems", selectedRandomEntryButton == "gamessystems"); randomEntryButton->add("DISABLED", "disabled", selectedRandomEntryButton == "disabled"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the random entry button to "games" in this case. if (randomEntryButton->getSelectedObjects().size() == 0) randomEntryButton->selectEntry(0); s->addWithLabel("RANDOM ENTRY BUTTON", randomEntryButton); s->addSaveFunc([randomEntryButton, s] { if (randomEntryButton->getSelected() != Settings::getInstance()->getString("RandomEntryButton")) { Settings::getInstance()->setString("RandomEntryButton", randomEntryButton->getSelected()); s->setNeedsSaving(); } }); // Media viewer. ComponentListRow mediaViewerRow; mediaViewerRow.elements.clear(); mediaViewerRow.addElement(std::make_shared("MEDIA VIEWER SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); mediaViewerRow.addElement(mMenu.makeArrow(), false); mediaViewerRow.makeAcceptInputHandler(std::bind(&GuiMenu::openMediaViewerOptions, this)); s->addRow(mediaViewerRow); // Screensaver. ComponentListRow screensaverRow; screensaverRow.elements.clear(); screensaverRow.addElement(std::make_shared("SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); screensaverRow.addElement(mMenu.makeArrow(), false); screensaverRow.makeAcceptInputHandler(std::bind(&GuiMenu::openScreensaverOptions, this)); s->addRow(screensaverRow); // Enable theme variant triggers. auto themeVariantTriggers = std::make_shared(); themeVariantTriggers->setState(Settings::getInstance()->getBool("ThemeVariantTriggers")); s->addWithLabel("ENABLE THEME VARIANT TRIGGERS", themeVariantTriggers); s->addSaveFunc([themeVariantTriggers, s] { if (themeVariantTriggers->getState() != Settings::getInstance()->getBool("ThemeVariantTriggers")) { Settings::getInstance()->setBool("ThemeVariantTriggers", themeVariantTriggers->getState()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); // Blur background when the menu is open. auto menuBlurBackground = std::make_shared(); if (mRenderer->getScreenRotation() == 90 || mRenderer->getScreenRotation() == 270) { // TODO: Add support for non-blurred background when rotating screen 90 or 270 degrees. menuBlurBackground->setState(true); s->addWithLabel("BLUR BACKGROUND WHEN MENU IS OPEN", menuBlurBackground); menuBlurBackground->setEnabled(false); menuBlurBackground->setOpacity(DISABLED_OPACITY); menuBlurBackground->getParent() ->getChild(menuBlurBackground->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } else { menuBlurBackground->setState(Settings::getInstance()->getBool("MenuBlurBackground")); s->addWithLabel("BLUR BACKGROUND WHEN MENU IS OPEN", menuBlurBackground); s->addSaveFunc([menuBlurBackground, s] { if (menuBlurBackground->getState() != Settings::getInstance()->getBool("MenuBlurBackground")) { Settings::getInstance()->setBool("MenuBlurBackground", menuBlurBackground->getState()); s->setNeedsSaving(); s->setInvalidateCachedBackground(); } }); } // Sort folders on top of the gamelists. auto foldersOnTop = std::make_shared(); foldersOnTop->setState(Settings::getInstance()->getBool("FoldersOnTop")); s->addWithLabel("SORT FOLDERS ON TOP OF GAMELISTS", foldersOnTop); s->addSaveFunc([foldersOnTop, s] { if (foldersOnTop->getState() != Settings::getInstance()->getBool("FoldersOnTop")) { Settings::getInstance()->setBool("FoldersOnTop", foldersOnTop->getState()); s->setNeedsSaving(); s->setNeedsSorting(); s->setInvalidateCachedBackground(); } }); // Sort favorites on top of non-favorites in the gamelists. auto favoritesFirst = std::make_shared(); favoritesFirst->setState(Settings::getInstance()->getBool("FavoritesFirst")); s->addWithLabel("SORT FAVORITE GAMES ABOVE NON-FAVORITES", favoritesFirst); s->addSaveFunc([favoritesFirst, s] { if (favoritesFirst->getState() != Settings::getInstance()->getBool("FavoritesFirst")) { Settings::getInstance()->setBool("FavoritesFirst", favoritesFirst->getState()); s->setNeedsSaving(); s->setNeedsSorting(); s->setNeedsSortingCollections(); s->setInvalidateCachedBackground(); } }); // Enable gamelist star markings for favorite games. auto favoritesStar = std::make_shared(); favoritesStar->setState(Settings::getInstance()->getBool("FavoritesStar")); s->addWithLabel("ADD STAR MARKINGS TO FAVORITE GAMES", favoritesStar); s->addSaveFunc([favoritesStar, s] { if (favoritesStar->getState() != Settings::getInstance()->getBool("FavoritesStar")) { Settings::getInstance()->setBool("FavoritesStar", favoritesStar->getState()); s->setNeedsSaving(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); // Enable quick list scrolling overlay. auto listScrollOverlay = std::make_shared(); listScrollOverlay->setState(Settings::getInstance()->getBool("ListScrollOverlay")); s->addWithLabel("ENABLE TEXTLIST QUICK SCROLLING OVERLAY", listScrollOverlay); s->addSaveFunc([listScrollOverlay, s] { if (listScrollOverlay->getState() != Settings::getInstance()->getBool("ListScrollOverlay")) { Settings::getInstance()->setBool("ListScrollOverlay", listScrollOverlay->getState()); s->setNeedsSaving(); } }); // Enable virtual (on-screen) keyboard. auto virtualKeyboard = std::make_shared(); virtualKeyboard->setState(Settings::getInstance()->getBool("VirtualKeyboard")); s->addWithLabel("ENABLE VIRTUAL KEYBOARD", virtualKeyboard); s->addSaveFunc([virtualKeyboard, s] { if (virtualKeyboard->getState() != Settings::getInstance()->getBool("VirtualKeyboard")) { Settings::getInstance()->setBool("VirtualKeyboard", virtualKeyboard->getState()); s->setNeedsSaving(); s->setInvalidateCachedBackground(); #if defined(__ANDROID__) if (Settings::getInstance()->getBool("VirtualKeyboard")) SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0"); else SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "1"); #endif } }); // Enable the 'Y' button for tagging games as favorites. auto favoritesAddButton = std::make_shared(); favoritesAddButton->setState(Settings::getInstance()->getBool("FavoritesAddButton")); s->addWithLabel("ENABLE TOGGLE FAVORITES BUTTON", favoritesAddButton); s->addSaveFunc([favoritesAddButton, s] { if (Settings::getInstance()->getBool("FavoritesAddButton") != favoritesAddButton->getState()) { Settings::getInstance()->setBool("FavoritesAddButton", favoritesAddButton->getState()); s->setNeedsSaving(); } }); // Gamelist filters. auto gamelistFilters = std::make_shared(); gamelistFilters->setState(Settings::getInstance()->getBool("GamelistFilters")); s->addWithLabel("ENABLE GAMELIST FILTERS", gamelistFilters); s->addSaveFunc([gamelistFilters, s] { if (Settings::getInstance()->getBool("GamelistFilters") != gamelistFilters->getState()) { Settings::getInstance()->setBool("GamelistFilters", gamelistFilters->getState()); s->setNeedsSaving(); s->setNeedsReloading(); } }); // On-screen help prompts. auto showHelpPrompts = std::make_shared(); showHelpPrompts->setState(Settings::getInstance()->getBool("ShowHelpPrompts")); s->addWithLabel("DISPLAY ON-SCREEN HELP", showHelpPrompts); s->addSaveFunc([showHelpPrompts, s] { if (Settings::getInstance()->getBool("ShowHelpPrompts") != showHelpPrompts->getState()) { Settings::getInstance()->setBool("ShowHelpPrompts", showHelpPrompts->getState()); s->setNeedsSaving(); } }); // When the theme entries are scrolled or selected, update the relevant rows. auto scrollThemeFunc = [=](const std::string& themeName, bool firstRun = false) { auto selectedTheme = themes.find(themeName); if (selectedTheme == themes.cend()) return; if (!firstRun) { themeVariantsFunc(themeName, themeVariant->getSelected()); themeColorSchemesFunc(themeName, themeColorScheme->getSelected()); themeFontSizeFunc(themeName, themeFontSize->getSelected()); themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected()); themeTransitionsFunc(themeName, themeTransitions->getSelected()); } int selectableVariants {0}; for (auto& variant : selectedTheme->second.capabilities.variants) { if (variant.selectable) ++selectableVariants; } if (selectableVariants > 0) { themeVariant->setEnabled(true); themeVariant->setOpacity(1.0f); themeVariant->getParent() ->getChild(themeVariant->getChildIndex() - 1) ->setOpacity(1.0f); } else { themeVariant->setEnabled(false); themeVariant->setOpacity(DISABLED_OPACITY); themeVariant->getParent() ->getChild(themeVariant->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } if (selectedTheme->second.capabilities.colorSchemes.size() > 0) { themeColorScheme->setEnabled(true); themeColorScheme->setOpacity(1.0f); themeColorScheme->getParent() ->getChild(themeColorScheme->getChildIndex() - 1) ->setOpacity(1.0f); } else { themeColorScheme->setEnabled(false); themeColorScheme->setOpacity(DISABLED_OPACITY); themeColorScheme->getParent() ->getChild(themeColorScheme->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } if (selectedTheme->second.capabilities.fontSizes.size() > 0) { themeFontSize->setEnabled(true); themeFontSize->setOpacity(1.0f); themeFontSize->getParent() ->getChild(themeFontSize->getChildIndex() - 1) ->setOpacity(1.0f); } else { themeFontSize->setEnabled(false); themeFontSize->setOpacity(DISABLED_OPACITY); themeFontSize->getParent() ->getChild(themeFontSize->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } if (selectedTheme->second.capabilities.aspectRatios.size() > 0) { themeAspectRatio->setEnabled(true); themeAspectRatio->setOpacity(1.0f); themeAspectRatio->getParent() ->getChild(themeAspectRatio->getChildIndex() - 1) ->setOpacity(1.0f); } else { themeAspectRatio->setEnabled(false); themeAspectRatio->setOpacity(DISABLED_OPACITY); themeAspectRatio->getParent() ->getChild(themeAspectRatio->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } }; scrollThemeFunc(selectedTheme->first, true); theme->setCallback(scrollThemeFunc); s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openSoundOptions() { auto s = new GuiSettings("SOUND SETTINGS"); // TODO: Implement system volume support for macOS and Android. #if !defined(__APPLE__) && !defined(__ANDROID__) && !defined(__FreeBSD__) && \ !defined(__OpenBSD__) && !defined(__NetBSD__) // System volume. // The reason to create the VolumeControl object every time instead of making it a singleton // is that this is the easiest way to detect new default audio devices or changes to the // audio volume done by the operating system. And we don't really need this object laying // around anyway as it's only used here. VolumeControl volumeControl; int currentVolume {volumeControl.getVolume()}; auto systemVolume = std::make_shared(0.0f, 100.0f, 1.0f, "%"); systemVolume->setValue(static_cast(currentVolume)); s->addWithLabel("SYSTEM VOLUME", systemVolume); s->addSaveFunc([systemVolume, currentVolume] { // No need to create the VolumeControl object unless the volume has actually been changed. if (static_cast(systemVolume->getValue()) != currentVolume) { VolumeControl volumeControl; volumeControl.setVolume(static_cast(std::round(systemVolume->getValue()))); } }); #endif // Volume for navigation sounds. auto soundVolumeNavigation = std::make_shared(0.0f, 100.0f, 1.0f, "%"); soundVolumeNavigation->setValue( static_cast(Settings::getInstance()->getInt("SoundVolumeNavigation"))); s->addWithLabel("NAVIGATION SOUNDS VOLUME", soundVolumeNavigation); s->addSaveFunc([soundVolumeNavigation, s] { if (soundVolumeNavigation->getValue() != static_cast(Settings::getInstance()->getInt("SoundVolumeNavigation"))) { Settings::getInstance()->setInt("SoundVolumeNavigation", static_cast(soundVolumeNavigation->getValue())); s->setNeedsSaving(); } }); // Volume for videos. auto soundVolumeVideos = std::make_shared(0.0f, 100.0f, 1.0f, "%"); soundVolumeVideos->setValue( static_cast(Settings::getInstance()->getInt("SoundVolumeVideos"))); s->addWithLabel("VIDEO PLAYER VOLUME", soundVolumeVideos); s->addSaveFunc([soundVolumeVideos, s] { if (soundVolumeVideos->getValue() != static_cast(Settings::getInstance()->getInt("SoundVolumeVideos"))) { Settings::getInstance()->setInt("SoundVolumeVideos", static_cast(soundVolumeVideos->getValue())); s->setNeedsSaving(); } }); if (UIModeController::getInstance()->isUIModeFull()) { // Play audio for gamelist videos. auto viewsVideoAudio = std::make_shared(); viewsVideoAudio->setState(Settings::getInstance()->getBool("ViewsVideoAudio")); s->addWithLabel("PLAY AUDIO FOR GAMELIST AND SYSTEM VIEW VIDEOS", viewsVideoAudio); s->addSaveFunc([viewsVideoAudio, s] { if (viewsVideoAudio->getState() != Settings::getInstance()->getBool("ViewsVideoAudio")) { Settings::getInstance()->setBool("ViewsVideoAudio", viewsVideoAudio->getState()); s->setNeedsSaving(); } }); // Play audio for media viewer videos. auto mediaViewerVideoAudio = std::make_shared(); mediaViewerVideoAudio->setState(Settings::getInstance()->getBool("MediaViewerVideoAudio")); s->addWithLabel("PLAY AUDIO FOR MEDIA VIEWER VIDEOS", mediaViewerVideoAudio); s->addSaveFunc([mediaViewerVideoAudio, s] { if (mediaViewerVideoAudio->getState() != Settings::getInstance()->getBool("MediaViewerVideoAudio")) { Settings::getInstance()->setBool("MediaViewerVideoAudio", mediaViewerVideoAudio->getState()); s->setNeedsSaving(); } }); // Play audio for screensaver videos. auto screensaverVideoAudio = std::make_shared(); screensaverVideoAudio->setState(Settings::getInstance()->getBool("ScreensaverVideoAudio")); s->addWithLabel("PLAY AUDIO FOR SCREENSAVER VIDEOS", screensaverVideoAudio); s->addSaveFunc([screensaverVideoAudio, s] { if (screensaverVideoAudio->getState() != Settings::getInstance()->getBool("ScreensaverVideoAudio")) { Settings::getInstance()->setBool("ScreensaverVideoAudio", screensaverVideoAudio->getState()); s->setNeedsSaving(); } }); // Navigation sounds. auto navigationSounds = std::make_shared(); navigationSounds->setState(Settings::getInstance()->getBool("NavigationSounds")); s->addWithLabel("ENABLE NAVIGATION SOUNDS", navigationSounds); s->addSaveFunc([navigationSounds, s] { if (navigationSounds->getState() != Settings::getInstance()->getBool("NavigationSounds")) { Settings::getInstance()->setBool("NavigationSounds", navigationSounds->getState()); s->setNeedsSaving(); } }); } s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openInputDeviceOptions() { auto s = new GuiSettings("INPUT DEVICE SETTINGS"); // Controller type. auto inputControllerType = std::make_shared>( getHelpStyle(), "CONTROLLER TYPE", false); std::string selectedPlayer {Settings::getInstance()->getString("InputControllerType")}; inputControllerType->add("XBOX", "xbox", selectedPlayer == "xbox"); inputControllerType->add("XBOX 360", "xbox360", selectedPlayer == "xbox360"); inputControllerType->add("PLAYSTATION 1/2/3", "ps123", selectedPlayer == "ps123"); inputControllerType->add("PLAYSTATION 4", "ps4", selectedPlayer == "ps4"); inputControllerType->add("PLAYSTATION 5", "ps5", selectedPlayer == "ps5"); inputControllerType->add("SWITCH PRO", "switchpro", selectedPlayer == "switchpro"); inputControllerType->add("SNES", "snes", selectedPlayer == "snes"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the controller type to "xbox" in this case. if (inputControllerType->getSelectedObjects().size() == 0) inputControllerType->selectEntry(0); s->addWithLabel("CONTROLLER TYPE", inputControllerType); s->addSaveFunc([inputControllerType, s] { if (inputControllerType->getSelected() != Settings::getInstance()->getString("InputControllerType")) { Settings::getInstance()->setString("InputControllerType", inputControllerType->getSelected()); s->setNeedsSaving(); } }); #if defined(__ANDROID__) // Touch overlay size. auto touchOverlaySize = std::make_shared>( getHelpStyle(), "TOUCH OVERLAY SIZE", false); std::string selectedOverlaySize {Settings::getInstance()->getString("InputTouchOverlaySize")}; touchOverlaySize->add("MEDIUM", "medium", selectedOverlaySize == "medium"); touchOverlaySize->add("LARGE", "large", selectedOverlaySize == "large"); touchOverlaySize->add("SMALL", "small", selectedOverlaySize == "small"); touchOverlaySize->add("EXTRA SMALL", "x-small", selectedOverlaySize == "x-small"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the overlay size to "medium" in this case. if (touchOverlaySize->getSelectedObjects().size() == 0) touchOverlaySize->selectEntry(0); s->addWithLabel("TOUCH OVERLAY SIZE", touchOverlaySize); s->addSaveFunc([touchOverlaySize, s] { if (touchOverlaySize->getSelected() != Settings::getInstance()->getString("InputTouchOverlaySize")) { Settings::getInstance()->setString("InputTouchOverlaySize", touchOverlaySize->getSelected()); s->setNeedsSaving(); InputOverlay::getInstance().createButtons(); } }); // Touch overlay opacity. auto touchOverlayOpacity = std::make_shared>( getHelpStyle(), "TOUCH OVERLAY OPACITY", false); std::string selectedOverlayOpacity { Settings::getInstance()->getString("InputTouchOverlayOpacity")}; touchOverlayOpacity->add("NORMAL", "normal", selectedOverlayOpacity == "normal"); touchOverlayOpacity->add("LOW", "low", selectedOverlayOpacity == "low"); touchOverlayOpacity->add("VERY LOW", "verylow", selectedOverlayOpacity == "verylow"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the overlay opacity to "normal" in this case. if (touchOverlayOpacity->getSelectedObjects().size() == 0) touchOverlayOpacity->selectEntry(0); s->addWithLabel("TOUCH OVERLAY OPACITY", touchOverlayOpacity); s->addSaveFunc([touchOverlayOpacity, s] { if (touchOverlayOpacity->getSelected() != Settings::getInstance()->getString("InputTouchOverlayOpacity")) { Settings::getInstance()->setString("InputTouchOverlayOpacity", touchOverlayOpacity->getSelected()); s->setNeedsSaving(); InputOverlay::getInstance().createButtons(); } }); // Touch overlay fade-out timer. auto touchOverlayFadeTime = std::make_shared(0.0f, 20.0f, 1.0f, "s"); touchOverlayFadeTime->setValue( static_cast(Settings::getInstance()->getInt("InputTouchOverlayFadeTime"))); s->addWithLabel("TOUCH OVERLAY FADE-OUT TIME", touchOverlayFadeTime); s->addSaveFunc([touchOverlayFadeTime, s] { if (touchOverlayFadeTime->getValue() != static_cast(Settings::getInstance()->getInt("InputTouchOverlayFadeTime"))) { Settings::getInstance()->setInt("InputTouchOverlayFadeTime", static_cast(touchOverlayFadeTime->getValue())); InputOverlay::getInstance().resetFadeTimer(); s->setNeedsSaving(); } }); // Whether to enable the touch overlay. auto inputTouchOverlay = std::make_shared(); inputTouchOverlay->setState(Settings::getInstance()->getBool("InputTouchOverlay")); s->addWithLabel("ENABLE TOUCH OVERLAY", inputTouchOverlay); s->addSaveFunc([inputTouchOverlay, s] { if (Settings::getInstance()->getBool("InputTouchOverlay") != inputTouchOverlay->getState()) { Settings::getInstance()->setBool("InputTouchOverlay", inputTouchOverlay->getState()); if (Settings::getInstance()->getBool("InputTouchOverlay")) InputOverlay::getInstance().createButtons(); else InputOverlay::getInstance().clearButtons(); s->setNeedsSaving(); } }); if (!Settings::getInstance()->getBool("InputTouchOverlay")) { touchOverlaySize->setEnabled(false); touchOverlaySize->setOpacity(DISABLED_OPACITY); touchOverlaySize->getParent() ->getChild(touchOverlaySize->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); touchOverlayOpacity->setEnabled(false); touchOverlayOpacity->setOpacity(DISABLED_OPACITY); touchOverlayOpacity->getParent() ->getChild(touchOverlayOpacity->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); touchOverlayFadeTime->setEnabled(false); touchOverlayFadeTime->setOpacity(DISABLED_OPACITY); touchOverlayFadeTime->getParent() ->getChild(touchOverlayFadeTime->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } auto inputTouchOverlayCallback = [this, inputTouchOverlay, touchOverlaySize, touchOverlayOpacity, touchOverlayFadeTime]() { if (!inputTouchOverlay->getState()) { const std::string message { "DON'T DISABLE THE TOUCH OVERLAY UNLESS YOU ARE USING A CONTROLLER OR YOU WILL " "LOCK YOURSELF OUT OF THE APP. IF THIS HAPPENS YOU WILL NEED TO TEMPORARILY " "PLUG IN A CONTROLLER OR A KEYBOARD TO ENABLE THIS SETTING AGAIN, OR YOU " "COULD CLEAR THE ES-DE STORAGE IN THE ANDROID APP SETTINGS TO FORCE THE " "CONFIGURATOR TO RUN ON NEXT STARTUP"}; Window* window {mWindow}; window->pushGui( new GuiMsgBox(getHelpStyle(), message, "OK", nullptr, "", nullptr, "", nullptr, nullptr, true, true, (mRenderer->getIsVerticalOrientation() ? 0.84f : 0.54f * (1.778f / mRenderer->getScreenAspectRatio())))); } if (touchOverlaySize->getEnabled()) { touchOverlaySize->setEnabled(false); touchOverlaySize->setOpacity(DISABLED_OPACITY); touchOverlaySize->getParent() ->getChild(touchOverlaySize->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); touchOverlayOpacity->setEnabled(false); touchOverlayOpacity->setOpacity(DISABLED_OPACITY); touchOverlayOpacity->getParent() ->getChild(touchOverlayOpacity->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); touchOverlayFadeTime->setEnabled(false); touchOverlayFadeTime->setOpacity(DISABLED_OPACITY); touchOverlayFadeTime->getParent() ->getChild(touchOverlayFadeTime->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } else { touchOverlaySize->setEnabled(true); touchOverlaySize->setOpacity(1.0f); touchOverlaySize->getParent() ->getChild(touchOverlaySize->getChildIndex() - 1) ->setOpacity(1.0f); touchOverlayOpacity->setEnabled(true); touchOverlayOpacity->setOpacity(1.0f); touchOverlayOpacity->getParent() ->getChild(touchOverlayOpacity->getChildIndex() - 1) ->setOpacity(1.0f); touchOverlayFadeTime->setEnabled(true); touchOverlayFadeTime->setOpacity(1.0f); touchOverlayFadeTime->getParent() ->getChild(touchOverlayFadeTime->getChildIndex() - 1) ->setOpacity(1.0f); } }; inputTouchOverlay->setCallback(inputTouchOverlayCallback); #endif // Whether to only accept input from the first controller. auto inputOnlyFirstController = std::make_shared(); inputOnlyFirstController->setState( Settings::getInstance()->getBool("InputOnlyFirstController")); s->addWithLabel("ONLY ACCEPT INPUT FROM FIRST CONTROLLER", inputOnlyFirstController); s->addSaveFunc([inputOnlyFirstController, s] { if (Settings::getInstance()->getBool("InputOnlyFirstController") != inputOnlyFirstController->getState()) { Settings::getInstance()->setBool("InputOnlyFirstController", inputOnlyFirstController->getState()); s->setNeedsSaving(); } }); // Whether to swap the A/B and X/Y buttons. auto inputSwapButtons = std::make_shared(); inputSwapButtons->setState(Settings::getInstance()->getBool("InputSwapButtons")); s->addWithLabel("SWAP THE A/B AND X/Y BUTTONS", inputSwapButtons); s->addSaveFunc([inputSwapButtons, s] { if (Settings::getInstance()->getBool("InputSwapButtons") != inputSwapButtons->getState()) { Settings::getInstance()->setBool("InputSwapButtons", inputSwapButtons->getState()); s->setNeedsSaving(); } }); // Whether to ignore keyboard input (except the quit shortcut). auto inputIgnoreKeyboard = std::make_shared(); inputIgnoreKeyboard->setState(Settings::getInstance()->getBool("InputIgnoreKeyboard")); s->addWithLabel("IGNORE KEYBOARD INPUT", inputIgnoreKeyboard); s->addSaveFunc([inputIgnoreKeyboard, s] { if (Settings::getInstance()->getBool("InputIgnoreKeyboard") != inputIgnoreKeyboard->getState()) { Settings::getInstance()->setBool("InputIgnoreKeyboard", inputIgnoreKeyboard->getState()); s->setNeedsSaving(); } }); // Configure keyboard and controllers. ComponentListRow configureInputRow; configureInputRow.elements.clear(); configureInputRow.addElement( std::make_shared("CONFIGURE KEYBOARD AND CONTROLLERS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); configureInputRow.addElement(mMenu.makeArrow(), false); configureInputRow.makeAcceptInputHandler(std::bind(&GuiMenu::openConfigInput, this, s)); s->addRow(configureInputRow); s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openConfigInput(GuiSettings* settings) { // Always save the settings before starting the input configuration, in case the // controller type was changed. settings->save(); // Also unset the save flag so that a double saving does not take place when closing // the input device settings menu later on. settings->setNeedsSaving(false); std::string message { "THE KEYBOARD AND CONTROLLERS ARE AUTOMATICALLY CONFIGURED, BUT USING THIS " "CONFIGURATION TOOL YOU CAN OVERRIDE THE DEFAULT BUTTON MAPPINGS (THIS WILL NOT " "AFFECT THE HELP PROMPTS)"}; Window* window {mWindow}; window->pushGui(new GuiMsgBox( getHelpStyle(), message, "PROCEED", [window] { window->pushGui(new GuiDetectDevice(false, false, nullptr)); }, "CANCEL", nullptr, "", nullptr, nullptr, false, true, (mRenderer->getIsVerticalOrientation() ? 0.84f : 0.54f * (1.778f / mRenderer->getScreenAspectRatio())))); } void GuiMenu::openOtherOptions() { auto s = new GuiSettings("OTHER SETTINGS"); // Alternative emulators GUI. ComponentListRow alternativeEmulatorsRow; alternativeEmulatorsRow.elements.clear(); alternativeEmulatorsRow.addElement(std::make_shared("ALTERNATIVE EMULATORS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); alternativeEmulatorsRow.addElement(mMenu.makeArrow(), false); alternativeEmulatorsRow.makeAcceptInputHandler( std::bind([this] { mWindow->pushGui(new GuiAlternativeEmulators); })); s->addRow(alternativeEmulatorsRow); // Game media directory. ComponentListRow rowMediaDir; auto mediaDirectory = std::make_shared( "GAME MEDIA DIRECTORY", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); auto bracketMediaDirectory = std::make_shared(); bracketMediaDirectory->setResize( glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); bracketMediaDirectory->setImage(":/graphics/arrow.svg"); bracketMediaDirectory->setColorShift(mMenuColorPrimary); rowMediaDir.addElement(mediaDirectory, true); rowMediaDir.addElement(bracketMediaDirectory, false); std::string titleMediaDir {"ENTER GAME MEDIA DIRECTORY"}; std::string mediaDirectoryStaticText {"Default directory:"}; std::string defaultDirectoryText {Utils::FileSystem::getAppDataDirectory() + "/downloaded_media"}; std::string initValueMediaDir {Settings::getInstance()->getString("MediaDirectory")}; bool multiLineMediaDir {false}; auto updateValMediaDir = [this](const std::string& newVal) { Settings::getInstance()->setString("MediaDirectory", newVal); Settings::getInstance()->saveFile(); ViewController::getInstance()->reloadAll(); mWindow->invalidateCachedBackground(); }; rowMediaDir.makeAcceptInputHandler([this, s, titleMediaDir, mediaDirectoryStaticText, defaultDirectoryText, initValueMediaDir, updateValMediaDir, multiLineMediaDir] { if (Settings::getInstance()->getBool("VirtualKeyboard")) { mWindow->pushGui(new GuiTextEditKeyboardPopup( getHelpStyle(), s->getMenu().getPosition().y, titleMediaDir, Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, defaultDirectoryText, "load default directory")); } else { mWindow->pushGui(new GuiTextEditPopup( getHelpStyle(), titleMediaDir, Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, defaultDirectoryText, "load default directory")); } }); s->addRow(rowMediaDir); // Maximum VRAM. auto maxVram = std::make_shared(128.0f, 2048.0f, 16.0f, "MiB"); maxVram->setValue(static_cast(Settings::getInstance()->getInt("MaxVRAM"))); s->addWithLabel("VRAM LIMIT", maxVram); s->addSaveFunc([maxVram, s] { if (maxVram->getValue() != Settings::getInstance()->getInt("MaxVRAM")) { Settings::getInstance()->setInt("MaxVRAM", static_cast(std::round(maxVram->getValue()))); s->setNeedsSaving(); } }); #if !defined(USE_OPENGLES) // Anti-aliasing (MSAA). auto antiAliasing = std::make_shared>( getHelpStyle(), "ANTI-ALIASING (MSAA)", false); const std::string& selectedAntiAliasing { std::to_string(Settings::getInstance()->getInt("AntiAliasing"))}; antiAliasing->add("DISABLED", "0", selectedAntiAliasing == "0"); antiAliasing->add("2X", "2", selectedAntiAliasing == "2"); antiAliasing->add("4X", "4", selectedAntiAliasing == "4"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set anti-aliasing to "0" in this case. if (antiAliasing->getSelectedObjects().size() == 0) antiAliasing->selectEntry(0); s->addWithLabel("ANTI-ALIASING (MSAA) (REQUIRES RESTART)", antiAliasing); s->addSaveFunc([antiAliasing, s] { if (antiAliasing->getSelected() != std::to_string(Settings::getInstance()->getInt("AntiAliasing"))) { Settings::getInstance()->setInt("AntiAliasing", atoi(antiAliasing->getSelected().c_str())); s->setNeedsSaving(); } }); #endif // Display/monitor. auto displayIndex = std::make_shared>( getHelpStyle(), "DISPLAY/MONITOR INDEX", false); std::vector displayIndexEntry; displayIndexEntry.push_back("1"); displayIndexEntry.push_back("2"); displayIndexEntry.push_back("3"); displayIndexEntry.push_back("4"); for (auto it = displayIndexEntry.cbegin(); it != displayIndexEntry.cend(); ++it) displayIndex->add(*it, *it, Settings::getInstance()->getInt("DisplayIndex") == atoi((*it).c_str())); s->addWithLabel("DISPLAY/MONITOR INDEX (REQUIRES RESTART)", displayIndex); s->addSaveFunc([displayIndex, s] { if (atoi(displayIndex->getSelected().c_str()) != Settings::getInstance()->getInt("DisplayIndex")) { Settings::getInstance()->setInt("DisplayIndex", atoi(displayIndex->getSelected().c_str())); s->setNeedsSaving(); } }); // Screen contents rotation. auto screenRotate = std::make_shared>(getHelpStyle(), "ROTATE SCREEN", false); const std::string& selectedScreenRotate { std::to_string(Settings::getInstance()->getInt("ScreenRotate"))}; screenRotate->add("DISABLED", "0", selectedScreenRotate == "0"); screenRotate->add("90 DEGREES", "90", selectedScreenRotate == "90"); screenRotate->add("180 DEGREES", "180", selectedScreenRotate == "180"); screenRotate->add("270 DEGREES", "270", selectedScreenRotate == "270"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set screen rotation to "0" in this case. if (screenRotate->getSelectedObjects().size() == 0) screenRotate->selectEntry(0); s->addWithLabel("ROTATE SCREEN (REQUIRES RESTART)", screenRotate); s->addSaveFunc([screenRotate, s] { if (screenRotate->getSelected() != std::to_string(Settings::getInstance()->getInt("ScreenRotate"))) { Settings::getInstance()->setInt("ScreenRotate", atoi(screenRotate->getSelected().c_str())); s->setNeedsSaving(); } }); // Keyboard quit shortcut. auto keyboardQuitShortcut = std::make_shared>( getHelpStyle(), "KEYBOARD QUIT SHORTCUT", false); std::string selectedShortcut {Settings::getInstance()->getString("KeyboardQuitShortcut")}; #if defined(_WIN64) || defined(__unix__) keyboardQuitShortcut->add("Alt + F4", "AltF4", selectedShortcut == "AltF4"); keyboardQuitShortcut->add("Ctrl + Q", "CtrlQ", selectedShortcut == "CtrlQ"); keyboardQuitShortcut->add("Alt + Q", "AltQ", selectedShortcut == "AltQ"); #endif #if defined(__APPLE__) keyboardQuitShortcut->add("\u2318 + Q", "CmdQ", selectedShortcut == "CmdQ"); keyboardQuitShortcut->add("Ctrl + Q", "CtrlQ", selectedShortcut == "CtrlQ"); keyboardQuitShortcut->add("Alt + Q", "AltQ", selectedShortcut == "AltQ"); #endif keyboardQuitShortcut->add("F4", "F4", selectedShortcut == "F4"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set the keyboard quit shortcut to the first entry in this case. if (keyboardQuitShortcut->getSelectedObjects().size() == 0) keyboardQuitShortcut->selectEntry(0); s->addWithLabel("KEYBOARD QUIT SHORTCUT", keyboardQuitShortcut); s->addSaveFunc([keyboardQuitShortcut, s] { if (keyboardQuitShortcut->getSelected() != Settings::getInstance()->getString("KeyboardQuitShortcut")) { Settings::getInstance()->setString("KeyboardQuitShortcut", keyboardQuitShortcut->getSelected()); s->setNeedsSaving(); } }); // When to save game metadata. auto saveGamelistsMode = std::make_shared>( getHelpStyle(), "WHEN TO SAVE METADATA", false); std::vector saveModes; saveModes.push_back("on exit"); saveModes.push_back("always"); saveModes.push_back("never"); for (auto it = saveModes.cbegin(); it != saveModes.cend(); ++it) { saveGamelistsMode->add(*it, *it, Settings::getInstance()->getString("SaveGamelistsMode") == *it); } s->addWithLabel("WHEN TO SAVE GAME METADATA", saveGamelistsMode); s->addSaveFunc([saveGamelistsMode, s] { if (saveGamelistsMode->getSelected() != Settings::getInstance()->getString("SaveGamelistsMode")) { Settings::getInstance()->setString("SaveGamelistsMode", saveGamelistsMode->getSelected()); // Always save the gamelist.xml files if switching to "always" as there may // be changes that will otherwise be lost. if (Settings::getInstance()->getString("SaveGamelistsMode") == "always") { for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it) (*it)->writeMetaData(); } s->setNeedsSaving(); } }); #if defined(APPLICATION_UPDATER) // Application updater frequency. auto applicationUpdaterFrequency = std::make_shared>( getHelpStyle(), "APPLICATION UPDATES", false); const std::string& selectedUpdaterFrequency { Settings::getInstance()->getString("ApplicationUpdaterFrequency")}; applicationUpdaterFrequency->add("ALWAYS", "always", selectedUpdaterFrequency == "always"); applicationUpdaterFrequency->add("DAILY", "daily", selectedUpdaterFrequency == "daily"); applicationUpdaterFrequency->add("WEEKLY", "weekly", selectedUpdaterFrequency == "weekly"); applicationUpdaterFrequency->add("MONTHLY", "monthly", selectedUpdaterFrequency == "monthly"); applicationUpdaterFrequency->add("NEVER", "never", selectedUpdaterFrequency == "never"); // If there are no objects returned, then there must be a manually modified entry in the // configuration file. Simply set updater frequency to "always" in this case. if (applicationUpdaterFrequency->getSelectedObjects().size() == 0) applicationUpdaterFrequency->selectEntry(0); s->addWithLabel("CHECK FOR APPLICATION UPDATES", applicationUpdaterFrequency); s->addSaveFunc([applicationUpdaterFrequency, s] { if (applicationUpdaterFrequency->getSelected() != Settings::getInstance()->getString("ApplicationUpdaterFrequency")) { Settings::getInstance()->setString("ApplicationUpdaterFrequency", applicationUpdaterFrequency->getSelected()); s->setNeedsSaving(); } }); #endif #if defined(APPLICATION_UPDATER) && !defined(__ANDROID__) #if defined(IS_PRERELEASE) // Add a dummy entry to indicate that this setting is always enabled when running a prerelease. auto applicationUpdaterPrereleases = std::make_shared(); applicationUpdaterPrereleases->setState(true); s->addWithLabel("INCLUDE PRERELEASES IN UPDATE CHECKS", applicationUpdaterPrereleases); applicationUpdaterPrereleases->setEnabled(false); applicationUpdaterPrereleases->setOpacity(DISABLED_OPACITY); applicationUpdaterPrereleases->getParent() ->getChild(applicationUpdaterPrereleases->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); #else // Whether to include prereleases when checking for application updates. auto applicationUpdaterPrereleases = std::make_shared(); applicationUpdaterPrereleases->setState( Settings::getInstance()->getBool("ApplicationUpdaterPrereleases")); s->addWithLabel("INCLUDE PRERELEASES IN UPDATE CHECKS", applicationUpdaterPrereleases); s->addSaveFunc([applicationUpdaterPrereleases, s] { if (applicationUpdaterPrereleases->getState() != Settings::getInstance()->getBool("ApplicationUpdaterPrereleases")) { Settings::getInstance()->setBool("ApplicationUpdaterPrereleases", applicationUpdaterPrereleases->getState()); s->setNeedsSaving(); } }); #endif #endif #if defined(_WIN64) // Hide taskbar during the program session. auto hide_taskbar = std::make_shared(); hide_taskbar->setState(Settings::getInstance()->getBool("HideTaskbar")); s->addWithLabel("HIDE TASKBAR (REQUIRES RESTART)", hide_taskbar); s->addSaveFunc([hide_taskbar, s] { if (hide_taskbar->getState() != Settings::getInstance()->getBool("HideTaskbar")) { Settings::getInstance()->setBool("HideTaskbar", hide_taskbar->getState()); s->setNeedsSaving(); } }); #endif #if !defined(__ANDROID__) && !defined(DEINIT_ON_LAUNCH) // Run ES in the background when a game has been launched. auto runInBackground = std::make_shared(); runInBackground->setState(Settings::getInstance()->getBool("RunInBackground")); s->addWithLabel("RUN IN BACKGROUND (WHILE GAME IS LAUNCHED)", runInBackground); s->addSaveFunc([runInBackground, s] { if (runInBackground->getState() != Settings::getInstance()->getBool("RunInBackground")) { Settings::getInstance()->setBool("RunInBackground", runInBackground->getState()); s->setNeedsSaving(); } }); #endif #if defined(VIDEO_HW_DECODING) // Whether to enable hardware decoding for the FFmpeg video player. auto videoHardwareDecoding = std::make_shared(); videoHardwareDecoding->setState(Settings::getInstance()->getBool("VideoHardwareDecoding")); s->addWithLabel("VIDEO HARDWARE DECODING (EXPERIMENTAL)", videoHardwareDecoding); s->addSaveFunc([videoHardwareDecoding, s] { if (videoHardwareDecoding->getState() != Settings::getInstance()->getBool("VideoHardwareDecoding")) { Settings::getInstance()->setBool("VideoHardwareDecoding", videoHardwareDecoding->getState()); s->setNeedsSaving(); } }); #endif // Whether to upscale the video frame rate to 60 FPS. auto videoUpscaleFrameRate = std::make_shared(); videoUpscaleFrameRate->setState(Settings::getInstance()->getBool("VideoUpscaleFrameRate")); s->addWithLabel("UPSCALE VIDEO FRAME RATE TO 60 FPS", videoUpscaleFrameRate); s->addSaveFunc([videoUpscaleFrameRate, s] { if (videoUpscaleFrameRate->getState() != Settings::getInstance()->getBool("VideoUpscaleFrameRate")) { Settings::getInstance()->setBool("VideoUpscaleFrameRate", videoUpscaleFrameRate->getState()); s->setNeedsSaving(); } }); // Whether to enable alternative emulators per game (the option to disable this is intended // primarily for testing purposes). auto alternativeEmulatorPerGame = std::make_shared(); 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(); s->setNeedsReloading(); s->setInvalidateCachedBackground(); } }); // Show hidden files. auto showHiddenFiles = std::make_shared(); showHiddenFiles->setState(Settings::getInstance()->getBool("ShowHiddenFiles")); s->addWithLabel("SHOW HIDDEN FILES AND FOLDERS", showHiddenFiles); s->addSaveFunc([this, showHiddenFiles, s] { if (showHiddenFiles->getState() != Settings::getInstance()->getBool("ShowHiddenFiles")) { Settings::getInstance()->setBool("ShowHiddenFiles", showHiddenFiles->getState()); s->setNeedsSaving(); s->setNeedsCloseMenu([this] { delete this; }); s->setNeedsRescanROMDirectory(); } }); // Show hidden games. auto showHiddenGames = std::make_shared(); showHiddenGames->setState(Settings::getInstance()->getBool("ShowHiddenGames")); s->addWithLabel("SHOW HIDDEN GAMES", showHiddenGames); s->addSaveFunc([this, showHiddenGames, s] { if (showHiddenGames->getState() != Settings::getInstance()->getBool("ShowHiddenGames")) { Settings::getInstance()->setBool("ShowHiddenGames", showHiddenGames->getState()); s->setNeedsSaving(); s->setNeedsCloseMenu([this] { delete this; }); s->setNeedsRescanROMDirectory(); } }); // Custom event scripts, fired using Scripting::fireEvent(). auto customEventScripts = std::make_shared(); customEventScripts->setState(Settings::getInstance()->getBool("CustomEventScripts")); s->addWithLabel("ENABLE CUSTOM EVENT SCRIPTS", customEventScripts); s->addSaveFunc([customEventScripts, s] { if (customEventScripts->getState() != Settings::getInstance()->getBool("CustomEventScripts")) { Settings::getInstance()->setBool("CustomEventScripts", customEventScripts->getState()); s->setNeedsSaving(); } }); // Only show games included in the gamelist.xml files. auto parseGamelistOnly = std::make_shared(); parseGamelistOnly->setState(Settings::getInstance()->getBool("ParseGamelistOnly")); s->addWithLabel("ONLY SHOW GAMES FROM GAMELIST.XML FILES", parseGamelistOnly); s->addSaveFunc([this, parseGamelistOnly, s] { if (parseGamelistOnly->getState() != Settings::getInstance()->getBool("ParseGamelistOnly")) { Settings::getInstance()->setBool("ParseGamelistOnly", parseGamelistOnly->getState()); s->setNeedsSaving(); s->setNeedsCloseMenu([this] { delete this; }); s->setNeedsRescanROMDirectory(); } }); // Strip extra MAME name info. auto mameNameStripExtraInfo = std::make_shared(); mameNameStripExtraInfo->setState(Settings::getInstance()->getBool("MAMENameStripExtraInfo")); s->addWithLabel("STRIP EXTRA MAME NAME INFO (REQUIRES RESTART)", mameNameStripExtraInfo); s->addSaveFunc([mameNameStripExtraInfo, s] { if (Settings::getInstance()->getBool("MAMENameStripExtraInfo") != mameNameStripExtraInfo->getState()) { Settings::getInstance()->setBool("MAMENameStripExtraInfo", mameNameStripExtraInfo->getState()); s->setNeedsSaving(); } }); #if defined(__unix__) && !defined(__ANDROID__) // Whether to disable desktop composition. auto disableComposition = std::make_shared(); disableComposition->setState(Settings::getInstance()->getBool("DisableComposition")); s->addWithLabel("DISABLE DESKTOP COMPOSITION (REQUIRES RESTART)", disableComposition); s->addSaveFunc([disableComposition, s] { if (disableComposition->getState() != Settings::getInstance()->getBool("DisableComposition")) { Settings::getInstance()->setBool("DisableComposition", disableComposition->getState()); s->setNeedsSaving(); } }); #endif #if defined(__ANDROID__) if (!AndroidVariables::sIsHomeApp) { // Whether swiping or pressing back should exit the application. auto backEventAppExit = std::make_shared(); backEventAppExit->setState(Settings::getInstance()->getBool("BackEventAppExit")); s->addWithLabel("BACK BUTTON/BACK SWIPE EXITS APP", backEventAppExit); s->addSaveFunc([backEventAppExit, s] { if (backEventAppExit->getState() != Settings::getInstance()->getBool("BackEventAppExit")) { Settings::getInstance()->setBool("BackEventAppExit", backEventAppExit->getState()); s->setNeedsSaving(); } }); } else { // If we're running as the Android home app then we don't allow the application to quit, // so simply add a disabled dummy switch in this case. auto backEventAppExit = std::make_shared(); s->addWithLabel("BACK BUTTON/BACK SWIPE EXITS APP", backEventAppExit); backEventAppExit->setEnabled(false); backEventAppExit->setState(false); backEventAppExit->setOpacity(DISABLED_OPACITY); backEventAppExit->getParent() ->getChild(backEventAppExit->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } #endif if (Settings::getInstance()->getBool("DebugFlag")) { // If the --debug command line option was passed then create a dummy entry. auto debugMode = std::make_shared(); debugMode->setState(true); s->addWithLabel("DEBUG MODE", debugMode); debugMode->setEnabled(false); debugMode->setOpacity(DISABLED_OPACITY); debugMode->getParent() ->getChild(debugMode->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } else { // Debug mode. auto debugMode = std::make_shared(); debugMode->setState(Settings::getInstance()->getBool("DebugMode")); s->addWithLabel("DEBUG MODE", debugMode); s->addSaveFunc([debugMode, s] { if (debugMode->getState() != Settings::getInstance()->getBool("DebugMode")) { if (!Settings::getInstance()->getBool("DebugMode")) { Settings::getInstance()->setBool("DebugMode", true); Settings::getInstance()->setBool("Debug", true); Log::setReportingLevel(LogDebug); } else { Settings::getInstance()->setBool("DebugMode", false); Settings::getInstance()->setBool("Debug", false); Log::setReportingLevel(LogInfo); } s->setNeedsSaving(); } }); } // GPU statistics overlay. auto displayGpuStatistics = std::make_shared(); displayGpuStatistics->setState(Settings::getInstance()->getBool("DisplayGPUStatistics")); s->addWithLabel("DISPLAY GPU STATISTICS OVERLAY", displayGpuStatistics); s->addSaveFunc([displayGpuStatistics, s] { if (displayGpuStatistics->getState() != Settings::getInstance()->getBool("DisplayGPUStatistics")) { Settings::getInstance()->setBool("DisplayGPUStatistics", displayGpuStatistics->getState()); s->setNeedsSaving(); } }); // Whether to enable the menu in Kid mode. auto enableMenuKidMode = std::make_shared(); enableMenuKidMode->setState(Settings::getInstance()->getBool("EnableMenuKidMode")); s->addWithLabel("ENABLE MENU IN KID MODE", enableMenuKidMode); s->addSaveFunc([enableMenuKidMode, s] { if (Settings::getInstance()->getBool("EnableMenuKidMode") != enableMenuKidMode->getState()) { Settings::getInstance()->setBool("EnableMenuKidMode", enableMenuKidMode->getState()); s->setNeedsSaving(); } }); // macOS requires root privileges to reboot and power off so it doesn't make much // sense to enable this setting and menu entry for that operating system. #if !defined(__APPLE__) && !defined(__ANDROID__) // Whether to show the quit menu with the options to reboot and shutdown the computer. auto showQuitMenu = std::make_shared(); showQuitMenu->setState(Settings::getInstance()->getBool("ShowQuitMenu")); s->addWithLabel("SHOW QUIT MENU (REBOOT AND POWER OFF ENTRIES)", showQuitMenu); s->addSaveFunc([this, showQuitMenu, s] { if (showQuitMenu->getState() != Settings::getInstance()->getBool("ShowQuitMenu")) { Settings::getInstance()->setBool("ShowQuitMenu", showQuitMenu->getState()); s->setNeedsSaving(); s->setNeedsCloseMenu([this] { delete this; }); } }); #endif #if defined(APPLICATION_UPDATER) && !defined(__ANDROID__) && !defined(IS_PRERELEASE) auto applicationUpdaterFrequencyFunc = [applicationUpdaterPrereleases](const std::string& frequency) { if (frequency == "never") { applicationUpdaterPrereleases->setEnabled(false); applicationUpdaterPrereleases->setOpacity(DISABLED_OPACITY); applicationUpdaterPrereleases->getParent() ->getChild(applicationUpdaterPrereleases->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } else { applicationUpdaterPrereleases->setEnabled(true); applicationUpdaterPrereleases->setOpacity(1.0f); applicationUpdaterPrereleases->getParent() ->getChild(applicationUpdaterPrereleases->getChildIndex() - 1) ->setOpacity(1.0f); } }; applicationUpdaterFrequencyFunc(applicationUpdaterFrequency->getSelected()); applicationUpdaterFrequency->setCallback(applicationUpdaterFrequencyFunc); #endif s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openUtilities() { auto s = new GuiSettings("UTILITIES"); HelpStyle style {getHelpStyle()}; ComponentListRow row; row.addElement(std::make_shared("ORPHANED DATA CLEANUP", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); row.addElement(mMenu.makeArrow(), false); row.makeAcceptInputHandler(std::bind( [this] { mWindow->pushGui(new GuiOrphanedDataCleanup([&]() { close(true); })); })); s->addRow(row); row.elements.clear(); row.addElement(std::make_shared("CREATE/UPDATE SYSTEM DIRECTORIES", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); // This transparent dummy arrow is only here to enable the "select" help prompt. auto dummyArrow = mMenu.makeArrow(); dummyArrow->setOpacity(0.0f); row.addElement(dummyArrow, false); row.makeAcceptInputHandler([this] { mWindow->pushGui(new GuiMsgBox( getHelpStyle(), "THIS WILL CREATE ALL GAME SYSTEM DIRECTORIES INSIDE YOUR ROM FOLDER AND IT WILL ALSO " "UPDATE ALL SYSTEMINFO.TXT FILES. THIS IS A SAFE OPERATION THAT WILL NOT DELETE OR " "MODIFY YOUR GAME FILES. TO DECREASE APPLICATION STARTUP TIMES IT'S RECOMMENDED TO " "DELETE THE SYSTEM DIRECTORIES YOU DON'T NEED AFTER RUNNING THIS UTILITY", "PROCEED", [this] { if (!SystemData::createSystemDirectories()) { mWindow->pushGui(new GuiMsgBox( getHelpStyle(), "THE SYSTEM DIRECTORIES WERE SUCCESSFULLY CREATED", "OK", [this] { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); mWindow->stopInfoPopup(); GuiMenu::close(true); // Write any gamelist.xml changes before proceeding with the rescan. if (Settings::getInstance()->getString("SaveGamelistsMode") == "on exit") { for (auto system : SystemData::sSystemVector) system->writeMetaData(); } ViewController::getInstance()->rescanROMDirectory(); }, "", nullptr, "", nullptr, nullptr, true)); } else { mWindow->pushGui( new GuiMsgBox(getHelpStyle(), "ERROR CREATING SYSTEM DIRECTORIES, PERMISSION PROBLEMS OR " "DISK FULL?\nSEE THE LOG FILE FOR MORE DETAILS", "OK", nullptr, "", nullptr, "", nullptr, nullptr, true, true, (mRenderer->getIsVerticalOrientation() ? 0.70f : 0.44f * (1.778f / mRenderer->getScreenAspectRatio())))); } }, "CANCEL", nullptr, "", nullptr, nullptr, false, true, (mRenderer->getIsVerticalOrientation() ? 0.80f : 0.52f * (1.778f / mRenderer->getScreenAspectRatio())))); }); s->addRow(row); row.elements.clear(); row.addElement(std::make_shared("RESCAN ROM DIRECTORY", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); // This transparent dummy arrow is only here to enable the "select" help prompt. row.addElement(dummyArrow, false); row.makeAcceptInputHandler([this] { mWindow->pushGui(new GuiMsgBox( getHelpStyle(), "THIS WILL RESCAN YOUR ROM DIRECTORY FOR CHANGES SUCH AS ADDED OR REMOVED GAMES AND " "SYSTEMS", "PROCEED", [this] { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); mWindow->stopInfoPopup(); GuiMenu::close(true); // Write any gamelist.xml changes before proceeding with the rescan. if (Settings::getInstance()->getString("SaveGamelistsMode") == "on exit") { for (auto system : SystemData::sSystemVector) system->writeMetaData(); } ViewController::getInstance()->rescanROMDirectory(); }, "CANCEL", nullptr, "", nullptr, nullptr, false, true, (mRenderer->getIsVerticalOrientation() ? 0.76f : 0.52f * (1.778f / mRenderer->getScreenAspectRatio())))); }); s->addRow(row); s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openESDEConfiguration() { // Create a new GuiSettings instance for the ES-DE Configurations menu auto s = new GuiSettings("ES-DE CONFIGURATIONS"); HelpStyle style{getHelpStyle()}; // UI SETTINGS ComponentListRow row; row.addElement(std::make_shared("UI SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); row.addElement(mMenu.makeArrow(), false); row.makeAcceptInputHandler([this] { openUIOptions(); }); s->addRow(row); // SOUND SETTINGS row.elements.clear(); row.addElement(std::make_shared("SOUND SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); row.addElement(mMenu.makeArrow(), false); row.makeAcceptInputHandler([this] { openSoundOptions(); }); s->addRow(row); // INPUT DEVICE SETTINGS row.elements.clear(); row.addElement(std::make_shared("INPUT DEVICE SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); row.addElement(mMenu.makeArrow(), false); row.makeAcceptInputHandler([this] { openInputDeviceOptions(); }); s->addRow(row); // OTHER SETTINGS row.elements.clear(); row.addElement(std::make_shared("OTHER SETTINGS", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true); row.addElement(mMenu.makeArrow(), false); row.makeAcceptInputHandler([this] { openOtherOptions(); }); s->addRow(row); // Set the size and push the menu onto the GUI stack s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openQuitMenu() { #if defined(__APPLE__) || defined(__ANDROID__) if (true) { #else if (!Settings::getInstance()->getBool("ShowQuitMenu")) { #endif mWindow->pushGui(new GuiMsgBox( this->getHelpStyle(), "REALLY QUIT?", "YES", [this] { close(true); Utils::Platform::quitES(); }, "NO", nullptr)); } else { auto s = new GuiSettings("QUIT"); Window* window {mWindow}; HelpStyle style {getHelpStyle()}; ComponentListRow row; row.makeAcceptInputHandler([window, this] { window->pushGui(new GuiMsgBox( this->getHelpStyle(), "REALLY QUIT?", "YES", [this] { close(true); Utils::Platform::quitES(); }, "NO", nullptr)); }); auto quitText = std::make_shared("QUIT RETRODECK", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); quitText->setSelectable(true); row.addElement(quitText, true); s->addRow(row); row.elements.clear(); row.makeAcceptInputHandler([window, this] { window->pushGui(new GuiMsgBox( this->getHelpStyle(), "REALLY REBOOT?", "YES", [] { if (Utils::Platform::quitES(Utils::Platform::QuitMode::REBOOT) != 0) { LOG(LogWarning) << "Reboot terminated with non-zero result!"; } }, "NO", nullptr)); }); auto rebootText = std::make_shared( "REBOOT SYSTEM", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); rebootText->setSelectable(true); row.addElement(rebootText, true); s->addRow(row); row.elements.clear(); row.makeAcceptInputHandler([window, this] { window->pushGui(new GuiMsgBox( this->getHelpStyle(), "REALLY POWER OFF?", "YES", [] { if (Utils::Platform::quitES(Utils::Platform::QuitMode::POWEROFF) != 0) { LOG(LogWarning) << "Power off terminated with non-zero result!"; } }, "NO", nullptr)); }); auto powerOffText = std::make_shared( "POWER OFF SYSTEM", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); powerOffText->setSelectable(true); row.addElement(powerOffText, true); s->addRow(row); s->setSize(mSize); mWindow->pushGui(s); } } void GuiMenu::addVersionInfo() { mVersion.setFont(Font::get(FONT_SIZE_SMALL)); mVersion.setColor(mMenuColorTertiary); const std::string applicationName {"RetroDECK"}; #if defined(IS_PRERELEASE) mVersion.setText(applicationName + " " + Utils::String::toUpper(PROGRAM_VERSION_STRING) + " (Built " + __DATE__ + ")"); # else #if defined(__ANDROID__) mVersion.setText(applicationName + " " + Utils::String::toUpper(PROGRAM_VERSION_STRING) + "-" + std::to_string(ANDROID_VERSION_CODE)); #else std::ifstream file("/app/retrodeck/version"); std::string version; std::getline(file, version); file.close(); #undef PROGRAM_VERSION_STRING #define PROGRAM_VERSION_STRING version.c_str() std::cout << PROGRAM_VERSION_STRING << std::endl; mVersion.setText(applicationName + " " + Utils::String::toUpper(PROGRAM_VERSION_STRING)); #endif #endif mVersion.setHorizontalAlignment(ALIGN_CENTER); addChild(&mVersion); } void GuiMenu::openThemeDownloader(GuiSettings* settings) { auto updateFunc = [&, settings]() { LOG(LogDebug) << "GuiMenu::openThemeDownloader(): Themes were updated, reloading menu"; mThemeDownloaderReloadCounter = 1; delete settings; if (mThemeDownloaderReloadCounter != 1) { delete this; } else { openUIOptions(); mWindow->invalidateCachedBackground(); } }; mWindow->pushGui(new GuiThemeDownloader(updateFunc)); } void GuiMenu::openMediaViewerOptions() { mWindow->pushGui(new GuiMediaViewerOptions("MEDIA VIEWER SETTINGS")); } void GuiMenu::openScreensaverOptions() { mWindow->pushGui(new GuiScreensaverOptions("SCREENSAVER SETTINGS")); } void GuiMenu::openCollectionSystemOptions() { mWindow->pushGui(new GuiCollectionSystemsOptions("GAME COLLECTION SETTINGS")); } void GuiMenu::onSizeChanged() { mVersion.setSize(mSize.x, 0.0f); mVersion.setPosition(0.0f, mSize.y - mVersion.getSize().y); } void GuiMenu::addEntry(const std::string& name, unsigned int color, bool add_arrow, const std::function& func) { std::shared_ptr font {Font::get(FONT_SIZE_MEDIUM)}; // Populate the list. ComponentListRow row; row.addElement(std::make_shared(name, font, color), true); if (add_arrow) { std::shared_ptr bracket {mMenu.makeArrow()}; row.addElement(bracket, false); } row.makeAcceptInputHandler(func); mMenu.addRow(row); } void GuiMenu::close(bool closeAllWindows) { std::function closeFunc; if (!closeAllWindows) { closeFunc = [this] { delete this; }; } else { Window* window {mWindow}; closeFunc = [window] { while (window->peekGui() != ViewController::getInstance()) delete window->peekGui(); }; } closeFunc(); } bool GuiMenu::input(InputConfig* config, Input input) { if (GuiComponent::input(config, input)) return true; const bool isStart {config->isMappedTo("start", input)}; if (input.value != 0 && (config->isMappedTo("b", input) || isStart)) { close(isStart); return true; } return false; } std::vector GuiMenu::getHelpPrompts() { std::vector prompts; prompts.push_back(HelpPrompt("up/down", "choose")); prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("b", "close menu")); prompts.push_back(HelpPrompt("start", "close menu")); return prompts; } void GuiMenu::openRetroDeckConfigurator() { // Launch the configurator.sh script std::string command; std::string startDirectory; bool runInBackground; command = "bash /app/tools/configurator.sh"; startDirectory = "/app/tools"; runInBackground = false; int result = Utils::Platform::launchGameUnix(command, startDirectory, runInBackground); // You can add any checks for the script's outcome here. }