// SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition // GuiSettings.cpp // // User interface template for a settings GUI. // The saving of es_settings.cfg, the reload of gamelists and some other actions are // also triggered to be executed here via flags set by the menu entries' lambda functions. // #include "guis/GuiSettings.h" #include "guis/GuiTextEditPopup.h" #include "views/gamelist/IGameListView.h" #include "views/ViewController.h" #include "CollectionSystemsManager.h" #include "FileFilterIndex.h" #include "Settings.h" #include "SystemData.h" #include "Window.h" GuiSettings::GuiSettings( Window* window, std::string title) : GuiComponent(window), mMenu(window, title), mNeedsSaving(false), mNeedsCollectionsUpdate(false), mNeedsSorting(false), mNeedsSortingCollections(false), mNeedsResetFilters(false), mNeedsReloading(false), mNeedsGoToStart(false), mNeedsGoToSystem(false), mNeedsGoToGroupedCollections(false), mInvalidateCachedBackground(false), mGoToSystem(nullptr) { addChild(&mMenu); mMenu.addButton("BACK", "back", [this] { delete this; }); setSize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f); } GuiSettings::~GuiSettings() { save(); } void GuiSettings::save() { if (!mSaveFuncs.size()) return; for (auto it = mSaveFuncs.cbegin(); it != mSaveFuncs.cend(); it++) (*it)(); if (mNeedsSaving) Settings::getInstance()->saveFile(); if (mNeedsCollectionsUpdate) { CollectionSystemsManager::get()->loadEnabledListFromSettings(); CollectionSystemsManager::get()->updateSystemsList(); } if (mNeedsSorting) { for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++) { if (!(!mNeedsSortingCollections && (*it)->isCollection())) { (*it)->sortSystem(true); } // Jump to the first row of the gamelist. IGameListView* gameList = ViewController::get()->getGameListView((*it)).get(); gameList->setCursor(gameList->getFirstEntry()); } } if (mNeedsResetFilters) { 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)->getIndex()->resetFilters(); } } if (mNeedsReloading) ViewController::get()->reloadAll(); if (mNeedsGoToStart) ViewController::get()->goToStart(); if (mNeedsGoToSystem) ViewController::get()->goToSystem(mGoToSystem, false); if (mNeedsGoToGroupedCollections) { bool groupedSystemExists = false; for (SystemData* system : SystemData::sSystemVector) { if (system->getThemeFolder() == "custom-collections") { ViewController::get()->goToSystem(system, false); groupedSystemExists = true; continue; } } if (!groupedSystemExists) // No grouped custom collection system exists, so go to the first system instead. ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false); } if (mNeedsCollectionsUpdate) { auto state = ViewController::get()->getState(); // If we're in any view other than the grouped custom collections, always jump to the // system view in case of any collection updates. This is overkill in some instances but // these views can behave a bit strange during collection changes so it's better to be on // the safe side. if (state.getSystem()->isCollection() && state.getSystem()->getThemeFolder() != "custom-collections") { ViewController::get()->goToStart(); ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false); // We don't want to invalidate the cached background when there has been a collection // systen change as that may show a black screen in some circumstances. return; } // If the last displayed custom collection was just disabled, then go to start (to the // system view). if (std::find(SystemData::sSystemVector.begin(), SystemData::sSystemVector.end(), state.getSystem()) == SystemData::sSystemVector.end()) { ViewController::get()->goToStart(); return; } } if (mInvalidateCachedBackground) { // This delay reduces the likelyhood that the SVG rasterizer which is running in a // separate thread is not done until the cached background is invalidated. Without // this delay there's a high chance that some theme elements are not rendered in // time and thus not getting included in the regenerated cached background. // This is just a hack though and a better mechanism is needed to handle this. SDL_Delay(100); if (mNeedsSaving || mNeedsCollectionsUpdate || mNeedsReloading || mNeedsSorting || mNeedsGoToStart || mNeedsGoToSystem) mWindow->invalidateCachedBackground(); } } void GuiSettings::addEditableTextComponent( const std::string label, std::shared_ptr ed, std::string value, std::string defaultValue, bool isPassword) { ComponentListRow row; row.elements.clear(); auto lbl = std::make_shared(mWindow, Utils::String::toUpper(label), Font::get(FONT_SIZE_MEDIUM), 0x777777FF); row.addElement(lbl, true); row.addElement(ed, true); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0); row.addElement(spacer, false); auto bracket = std::make_shared(mWindow); bracket->setImage(":/graphics/arrow.svg"); bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight())); row.addElement(bracket, false); // OK callback (apply new value to ed). auto updateVal = [ed, defaultValue, isPassword](const std::string& newVal) { // If the field is blank, apply the default value if it's been passes as an argument. if (defaultValue != "" && newVal == "") { ed->setValue(defaultValue); } // If it's a password and actually set to something, then show a star mask. else if (isPassword && newVal == "") { ed->setValue(""); ed->setHiddenValue(""); } else if (isPassword) { ed->setValue("********"); ed->setHiddenValue(newVal); } else { ed->setValue(newVal); } }; row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { // Never display the value if it's a password, instead set it to blank. if (isPassword) mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, false)); else mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, ed->getValue(), updateVal, false)); }); assert(ed); addRow(row); ed->setValue(value); } bool GuiSettings::input(InputConfig* config, Input input) { if (config->isMappedTo("b", input) && input.value != 0) { delete this; return true; } // Keep code for potential future use. // if (config->isMappedTo("start", input) && input.value != 0) { // // Close everything. // Window* window = mWindow; // while (window->peekGui() && window->peekGui() != ViewController::get()) // delete window->peekGui(); // return true; // } return GuiComponent::input(config, input); } HelpStyle GuiSettings::getHelpStyle() { HelpStyle style = HelpStyle(); style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system"); return style; } std::vector GuiSettings::getHelpPrompts() { std::vector prompts = mMenu.getHelpPrompts(); prompts.push_back(HelpPrompt("b", "back")); return prompts; }