mirror of
				https://github.com/RetroDECK/ES-DE.git
				synced 2025-04-10 19:15:13 +00:00 
			
		
		
		
	Added a virtual keyboard.
This commit is contained in:
		
							parent
							
								
									bbaf2739d4
								
							
						
					
					
						commit
						c4e6d3cac1
					
				|  | @ -14,6 +14,7 @@ | |||
| #include "components/SwitchComponent.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "guis/GuiSettings.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "utils/StringUtil.h" | ||||
| #include "views/ViewController.h" | ||||
|  | @ -208,10 +209,21 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st | |||
|         window->removeGui(topGui); | ||||
|         createCustomCollection(name); | ||||
|     }; | ||||
|     row.makeAcceptInputHandler([this, createCollectionCall] { | ||||
|         mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", "", | ||||
|                                               createCollectionCall, false, "SAVE")); | ||||
|     }); | ||||
| 
 | ||||
|     if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|         row.makeAcceptInputHandler([this, createCollectionCall] { | ||||
|             mWindow->pushGui(new GuiTextEditKeyboardPopup( | ||||
|                 mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, | ||||
|                 "CREATE", "CREATE COLLECTION?")); | ||||
|         }); | ||||
|     } | ||||
|     else { | ||||
|         row.makeAcceptInputHandler([this, createCollectionCall] { | ||||
|             mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", | ||||
|                                                   "", createCollectionCall, false, "CREATE", | ||||
|                                                   "CREATE COLLECTION?")); | ||||
|         }); | ||||
|     } | ||||
|     addRow(row); | ||||
| 
 | ||||
|     // Delete custom collection.
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| 
 | ||||
| #include "SystemData.h" | ||||
| #include "components/OptionListComponent.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "views/UIModeController.h" | ||||
| #include "views/ViewController.h" | ||||
|  | @ -118,11 +119,20 @@ void GuiGamelistFilter::addFiltersToMenu() | |||
|         mFilterIndex->setTextFilter(Utils::String::toUpper(newVal)); | ||||
|     }; | ||||
| 
 | ||||
|     row.makeAcceptInputHandler([this, updateVal] { | ||||
|         mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "TEXT FILTER (GAME NAME)", | ||||
|                                               mTextFilterField->getValue(), updateVal, false, "OK", | ||||
|                                               "APPLY CHANGES?")); | ||||
|     }); | ||||
|     if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|         row.makeAcceptInputHandler([this, updateVal] { | ||||
|             mWindow->pushGui(new GuiTextEditKeyboardPopup( | ||||
|                 mWindow, getHelpStyle(), "TEXT FILTER (GAME NAME)", mTextFilterField->getValue(), | ||||
|                 updateVal, false, "OK", "APPLY CHANGES?")); | ||||
|         }); | ||||
|     } | ||||
|     else { | ||||
|         row.makeAcceptInputHandler([this, updateVal] { | ||||
|             mWindow->pushGui(new GuiTextEditPopup( | ||||
|                 mWindow, getHelpStyle(), "TEXT FILTER (GAME NAME)", mTextFilterField->getValue(), | ||||
|                 updateVal, false, "OK", "APPLY CHANGES?")); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     mMenu.addRow(row); | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,12 +22,13 @@ | |||
| #include "components/SwitchComponent.h" | ||||
| #include "guis/GuiAlternativeEmulators.h" | ||||
| #include "guis/GuiCollectionSystemsOptions.h" | ||||
| #include "guis/GuiComplexTextEditPopup.h" | ||||
| #include "guis/GuiDetectDevice.h" | ||||
| #include "guis/GuiMediaViewerOptions.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "guis/GuiScraperMenu.h" | ||||
| #include "guis/GuiScreensaverOptions.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "views/UIModeController.h" | ||||
| #include "views/ViewController.h" | ||||
| #include "views/gamelist/IGameListView.h" | ||||
|  | @ -509,6 +510,18 @@ void GuiMenu::openUIOptions() | |||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Enable virtual (on-screen) keyboard.
 | ||||
|     auto virtual_keyboard = std::make_shared<SwitchComponent>(mWindow); | ||||
|     virtual_keyboard->setState(Settings::getInstance()->getBool("VirtualKeyboard")); | ||||
|     s->addWithLabel("ENABLE VIRTUAL KEYBOARD", virtual_keyboard); | ||||
|     s->addSaveFunc([virtual_keyboard, s] { | ||||
|         if (virtual_keyboard->getState() != Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|             Settings::getInstance()->setBool("VirtualKeyboard", virtual_keyboard->getState()); | ||||
|             s->setNeedsSaving(); | ||||
|             s->setInvalidateCachedBackground(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Enable the 'Y' button for tagging games as favorites.
 | ||||
|     auto favorites_add_button = std::make_shared<SwitchComponent>(mWindow); | ||||
|     favorites_add_button->setState(Settings::getInstance()->getBool("FavoritesAddButton")); | ||||
|  | @ -809,10 +822,20 @@ void GuiMenu::openOtherOptions() | |||
|     rowMediaDir.makeAcceptInputHandler([this, titleMediaDir, mediaDirectoryStaticText, | ||||
|                                         defaultDirectoryText, initValueMediaDir, updateValMediaDir, | ||||
|                                         multiLineMediaDir] { | ||||
|         mWindow->pushGui(new GuiComplexTextEditPopup( | ||||
|             mWindow, getHelpStyle(), titleMediaDir, mediaDirectoryStaticText, defaultDirectoryText, | ||||
|             Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, | ||||
|             multiLineMediaDir, "SAVE", "SAVE CHANGES?")); | ||||
|         if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|             mWindow->pushGui(new GuiTextEditKeyboardPopup( | ||||
|                 mWindow, getHelpStyle(), titleMediaDir, | ||||
|                 Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, | ||||
|                 multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, | ||||
|                 defaultDirectoryText, "load default directory")); | ||||
|         } | ||||
|         else { | ||||
|             mWindow->pushGui(new GuiTextEditPopup( | ||||
|                 mWindow, getHelpStyle(), titleMediaDir, | ||||
|                 Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, | ||||
|                 multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, | ||||
|                 defaultDirectoryText, "load default directory")); | ||||
|         } | ||||
|     }); | ||||
|     s->addRow(rowMediaDir); | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,9 +23,9 @@ | |||
| #include "components/RatingComponent.h" | ||||
| #include "components/SwitchComponent.h" | ||||
| #include "components/TextComponent.h" | ||||
| #include "guis/GuiComplexTextEditPopup.h" | ||||
| #include "guis/GuiGameScraper.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "resources/Font.h" | ||||
| #include "utils/StringUtil.h" | ||||
|  | @ -364,11 +364,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, | |||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { | ||||
|                     mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, | ||||
|                                                           ed->getValue(), updateVal, multiLine, | ||||
|                                                           "APPLY", "APPLY CHANGES?")); | ||||
|                 }); | ||||
|                 if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|                     row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { | ||||
|                         mWindow->pushGui(new GuiTextEditKeyboardPopup( | ||||
|                             mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, | ||||
|                             "apply", "APPLY CHANGES?", "", "")); | ||||
|                     }); | ||||
|                 } | ||||
|                 else { | ||||
|                     row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { | ||||
|                         mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, | ||||
|                                                               ed->getValue(), updateVal, multiLine, | ||||
|                                                               "APPLY", "APPLY CHANGES?")); | ||||
|                     }); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ | |||
| #include "components/ScrollableContainer.h" | ||||
| #include "components/TextComponent.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "resources/Font.h" | ||||
| #include "utils/StringUtil.h" | ||||
|  | @ -808,8 +809,16 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) | |||
|         searchString = params.nameOverride; | ||||
|     } | ||||
| 
 | ||||
|     mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, | ||||
|                                           searchForFunc, false, "SEARCH", "APPLY CHANGES?")); | ||||
|     if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|         mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), "REFINE SEARCH", | ||||
|                                                       searchString, searchForFunc, false, "SEARCH", | ||||
|                                                       "SEARCH USING REFINED NAME?")); | ||||
|     } | ||||
|     else { | ||||
|         mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", | ||||
|                                               searchString, searchForFunc, false, "SEARCH", | ||||
|                                               "SEARCH USING REFINED NAME?")); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result, | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| #include "SystemData.h" | ||||
| #include "Window.h" | ||||
| #include "components/HelpComponent.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "views/ViewController.h" | ||||
| #include "views/gamelist/IGameListView.h" | ||||
|  | @ -193,15 +194,30 @@ void GuiSettings::addEditableTextComponent(const std::string label, | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     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)); | ||||
|     }); | ||||
|     if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|         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 GuiTextEditKeyboardPopup( | ||||
|                     mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?")); | ||||
|             else | ||||
|                 mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), label, | ||||
|                                                               ed->getValue(), updateVal, false, | ||||
|                                                               "SAVE", "SAVE CHANGES?")); | ||||
|         }); | ||||
|     } | ||||
|     else { | ||||
|         row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { | ||||
|             if (isPassword) | ||||
|                 mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, | ||||
|                                                       false, "SAVE", "SAVE CHANGES?")); | ||||
|             else | ||||
|                 mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, | ||||
|                                                       ed->getValue(), updateVal, false, "SAVE", | ||||
|                                                       "SAVE CHANGES?")); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     assert(ed); | ||||
|     addRow(row); | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,6 +26,8 @@ | |||
| #include "animations/MoveCameraAnimation.h" | ||||
| #include "guis/GuiInfoPopup.h" | ||||
| #include "guis/GuiMenu.h" | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| #include "views/SystemView.h" | ||||
| #include "views/UIModeController.h" | ||||
| #include "views/gamelist/DetailedGameListView.h" | ||||
|  | @ -135,26 +137,52 @@ void ViewController::noGamesDialog() | |||
| #else | ||||
|             currentROMDirectory = FileData::getROMDirectory(); | ||||
| #endif | ||||
| 
 | ||||
|             mWindow->pushGui(new GuiComplexTextEditPopup( | ||||
|                 mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", | ||||
|                 "Currently configured path:", currentROMDirectory, currentROMDirectory, | ||||
|                 [this](const std::string& newROMDirectory) { | ||||
|                     Settings::getInstance()->setString("ROMDirectory", newROMDirectory); | ||||
|                     Settings::getInstance()->saveFile(); | ||||
|             if (Settings::getInstance()->getBool("VirtualKeyboard")) { | ||||
|                 mWindow->pushGui(new GuiTextEditKeyboardPopup( | ||||
|                     mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", currentROMDirectory, | ||||
|                     [this](const std::string& newROMDirectory) { | ||||
|                         Settings::getInstance()->setString("ROMDirectory", newROMDirectory); | ||||
|                         Settings::getInstance()->saveFile(); | ||||
| #if defined(_WIN64) | ||||
|                     mRomDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); | ||||
|                         mRomDirectory = | ||||
|                             Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); | ||||
| #else | ||||
|                     mRomDirectory = FileData::getROMDirectory(); | ||||
|                         mRomDirectory = FileData::getROMDirectory(); | ||||
| #endif | ||||
|                     mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory); | ||||
|                     mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), | ||||
|                                                    "ROM DIRECTORY SETTING SAVED, RESTART\n" | ||||
|                                                    "THE APPLICATION TO RESCAN THE SYSTEMS", | ||||
|                                                    "OK", nullptr, "", nullptr, "", nullptr, true)); | ||||
|                 }, | ||||
|                 false, "SAVE", "SAVE CHANGES?", "LOAD CURRENT", "LOAD CURRENTLY CONFIGURED VALUE", | ||||
|                 "CLEAR", "CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)", false)); | ||||
|                         mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory); | ||||
|                         mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), | ||||
|                                                        "ROM DIRECTORY SETTING SAVED, RESTART\n" | ||||
|                                                        "THE APPLICATION TO RESCAN THE SYSTEMS", | ||||
|                                                        "OK", nullptr, "", nullptr, "", nullptr, | ||||
|                                                        true)); | ||||
|                     }, | ||||
|                     false, "SAVE", "SAVE CHANGES?", "Currently configured path:", | ||||
|                     currentROMDirectory, "LOAD CURRENTLY CONFIGURED PATH", | ||||
|                     "CLEAR (LEAVE BLANK TO RESET TO DEFAULT PATH)")); | ||||
|             } | ||||
|             else { | ||||
|                 mWindow->pushGui(new GuiTextEditPopup( | ||||
|                     mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", currentROMDirectory, | ||||
|                     [this](const std::string& newROMDirectory) { | ||||
|                         Settings::getInstance()->setString("ROMDirectory", newROMDirectory); | ||||
|                         Settings::getInstance()->saveFile(); | ||||
| #if defined(_WIN64) | ||||
|                         mRomDirectory = | ||||
|                             Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); | ||||
| #else | ||||
|                         mRomDirectory = FileData::getROMDirectory(); | ||||
| #endif | ||||
|                         mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory); | ||||
|                         mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), | ||||
|                                                        "ROM DIRECTORY SETTING SAVED, RESTART\n" | ||||
|                                                        "THE APPLICATION TO RESCAN THE SYSTEMS", | ||||
|                                                        "OK", nullptr, "", nullptr, "", nullptr, | ||||
|                                                        true)); | ||||
|                     }, | ||||
|                     false, "SAVE", "SAVE CHANGES?", "Currently configured path:", | ||||
|                     currentROMDirectory, "LOAD CURRENTLY CONFIGURED PATH", | ||||
|                     "CLEAR (LEAVE BLANK TO RESET TO DEFAULT PATH)")); | ||||
|             } | ||||
|         }, | ||||
|         "CREATE DIRECTORIES", | ||||
|         [this] { | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ | |||
| 
 | ||||
| #include "FileData.h" | ||||
| #include "GuiComponent.h" | ||||
| #include "guis/GuiComplexTextEditPopup.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "renderers/Renderer.h" | ||||
| 
 | ||||
|  |  | |||
|  | @ -60,10 +60,10 @@ set(CORE_HEADERS | |||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h | ||||
| 
 | ||||
|         # GUIs | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiComplexTextEditPopup.h | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.h | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.h | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.h | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.h | ||||
| 
 | ||||
|         # Renderers | ||||
|  | @ -130,10 +130,10 @@ set(CORE_SOURCES | |||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp | ||||
| 
 | ||||
|         # GUIs | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiComplexTextEditPopup.cpp | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.cpp | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.cpp | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.cpp | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.cpp | ||||
| 
 | ||||
|         # Renderer | ||||
|  |  | |||
|  | @ -180,6 +180,7 @@ void Settings::setDefaults() | |||
|     mBoolMap["FavoritesStar"] = {true, true}; | ||||
|     mBoolMap["SpecialCharsASCII"] = {false, false}; | ||||
|     mBoolMap["ListScrollOverlay"] = {false, false}; | ||||
|     mBoolMap["VirtualKeyboard"] = {true, true}; | ||||
|     mBoolMap["FavoritesAddButton"] = {true, true}; | ||||
|     mBoolMap["RandomAddButton"] = {false, false}; | ||||
|     mBoolMap["GamelistFilters"] = {true, true}; | ||||
|  |  | |||
|  | @ -1,155 +0,0 @@ | |||
| //  SPDX-License-Identifier: MIT
 | ||||
| //
 | ||||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiComplexTextEditPopup.cpp
 | ||||
| //
 | ||||
| //  Text edit popup with a title, two text strings, a text input box and buttons
 | ||||
| //  to load the second text string and to clear the input field.
 | ||||
| //  Intended for updating settings for configuration files and similar.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "guis/GuiComplexTextEditPopup.h" | ||||
| 
 | ||||
| #include "Window.h" | ||||
| #include "components/ButtonComponent.h" | ||||
| #include "components/MenuComponent.h" | ||||
| #include "components/TextEditComponent.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| 
 | ||||
| GuiComplexTextEditPopup::GuiComplexTextEditPopup( | ||||
|     Window* window, | ||||
|     const HelpStyle& helpstyle, | ||||
|     const std::string& title, | ||||
|     const std::string& infoString1, | ||||
|     const std::string& infoString2, | ||||
|     const std::string& initValue, | ||||
|     const std::function<void(const std::string&)>& okCallback, | ||||
|     bool multiLine, | ||||
|     const std::string& acceptBtnText, | ||||
|     const std::string& saveConfirmationText, | ||||
|     const std::string& loadBtnText, | ||||
|     const std::string& loadBtnHelpText, | ||||
|     const std::string& clearBtnText, | ||||
|     const std::string& clearBtnHelpText, | ||||
|     bool hideCancelButton) | ||||
|     : GuiComponent(window) | ||||
|     , mHelpStyle(helpstyle) | ||||
|     , mBackground(window, ":/graphics/frame.svg") | ||||
|     , mGrid(window, glm::ivec2{1, 5}) | ||||
|     , mMultiLine(multiLine) | ||||
|     , mInitValue(initValue) | ||||
|     , mOkCallback(okCallback) | ||||
|     , mSaveConfirmationText(saveConfirmationText) | ||||
|     , mHideCancelButton(hideCancelButton) | ||||
| { | ||||
|     addChild(&mBackground); | ||||
|     addChild(&mGrid); | ||||
| 
 | ||||
|     mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title), | ||||
|                                              Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); | ||||
|     mInfoString1 = std::make_shared<TextComponent>(mWindow, infoString1, Font::get(FONT_SIZE_SMALL), | ||||
|                                                    0x555555FF, ALIGN_CENTER); | ||||
|     mInfoString2 = std::make_shared<TextComponent>(mWindow, infoString2, Font::get(FONT_SIZE_SMALL), | ||||
|                                                    0x555555FF, ALIGN_CENTER); | ||||
| 
 | ||||
|     mText = std::make_shared<TextEditComponent>(mWindow); | ||||
|     mText->setValue(initValue); | ||||
| 
 | ||||
|     std::vector<std::shared_ptr<ButtonComponent>> buttons; | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, acceptBtnText, acceptBtnText, | ||||
|                                                         [this, okCallback] { | ||||
|                                                             okCallback(mText->getValue()); | ||||
|                                                             delete this; | ||||
|                                                         })); | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, loadBtnText, loadBtnHelpText, | ||||
|                                                         [this, infoString2] { | ||||
|                                                             mText->setValue(infoString2); | ||||
|                                                             mText->setCursor(0); | ||||
|                                                             mText->setCursor(infoString2.size()); | ||||
|                                                         })); | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, clearBtnText, clearBtnHelpText, | ||||
|                                                         [this] { mText->setValue(""); })); | ||||
|     if (!mHideCancelButton) | ||||
|         buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes", | ||||
|                                                             [this] { delete this; })); | ||||
| 
 | ||||
|     mButtonGrid = makeButtonGrid(mWindow, buttons); | ||||
| 
 | ||||
|     mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); | ||||
|     mGrid.setEntry(mInfoString1, glm::ivec2{0, 1}, false, true); | ||||
|     mGrid.setEntry(mInfoString2, glm::ivec2{0, 2}, false, false); | ||||
|     mGrid.setEntry(mText, glm::ivec2{0, 3}, true, false, glm::ivec2{1, 1}, | ||||
|                    GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); | ||||
|     mGrid.setEntry(mButtonGrid, glm::ivec2{0, 4}, true, false); | ||||
|     mGrid.setRowHeightPerc(1, 0.15f, true); | ||||
| 
 | ||||
|     float textHeight = mText->getFont()->getHeight(); | ||||
| 
 | ||||
|     if (multiLine) | ||||
|         textHeight *= 6.0f; | ||||
| 
 | ||||
|     // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent
 | ||||
|     // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
 | ||||
|     float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); | ||||
|     float infoWidth = glm::clamp(0.70f * aspectValue, 0.60f, 0.85f) * Renderer::getScreenWidth(); | ||||
|     float windowWidth = glm::clamp(0.75f * aspectValue, 0.65f, 0.90f) * Renderer::getScreenWidth(); | ||||
| 
 | ||||
|     mText->setSize(0, textHeight); | ||||
|     mInfoString2->setSize(infoWidth, mInfoString2->getFont()->getHeight()); | ||||
| 
 | ||||
|     setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + | ||||
|                              mButtonGrid->getSize().y * 1.85f); | ||||
|     setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, | ||||
|                 (Renderer::getScreenHeight() - mSize.y) / 2.0f); | ||||
|     mText->startEditing(); | ||||
| } | ||||
| 
 | ||||
| void GuiComplexTextEditPopup::onSizeChanged() | ||||
| { | ||||
|     mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); | ||||
|     mText->setSize(mSize.x - 40.0f, mText->getSize().y); | ||||
| 
 | ||||
|     // Update grid.
 | ||||
|     mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y); | ||||
|     mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y / mSize.y); | ||||
|     mGrid.setSize(mSize); | ||||
| } | ||||
| 
 | ||||
| bool GuiComplexTextEditPopup::input(InputConfig* config, Input input) | ||||
| { | ||||
|     if (GuiComponent::input(config, input)) | ||||
|         return true; | ||||
| 
 | ||||
|     if (!mHideCancelButton) { | ||||
|         // Pressing back when not text editing closes us.
 | ||||
|         if (config->isMappedTo("b", input) && input.value) { | ||||
|             if (mText->getValue() != mInitValue) { | ||||
|                 // Changes were made, ask if the user wants to save them.
 | ||||
|                 mWindow->pushGui(new GuiMsgBox( | ||||
|                     mWindow, mHelpStyle, mSaveConfirmationText, "YES", | ||||
|                     [this] { | ||||
|                         this->mOkCallback(mText->getValue()); | ||||
|                         delete this; | ||||
|                         return true; | ||||
|                     }, | ||||
|                     "NO", | ||||
|                     [this] { | ||||
|                         delete this; | ||||
|                         return false; | ||||
|                     })); | ||||
|             } | ||||
|             else { | ||||
|                 delete this; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| std::vector<HelpPrompt> GuiComplexTextEditPopup::getHelpPrompts() | ||||
| { | ||||
|     std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts(); | ||||
|     if (!mHideCancelButton) | ||||
|         prompts.push_back(HelpPrompt("b", "back")); | ||||
|     return prompts; | ||||
| } | ||||
|  | @ -1,64 +0,0 @@ | |||
| //  SPDX-License-Identifier: MIT
 | ||||
| //
 | ||||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiComplexTextEditPopup.h
 | ||||
| //
 | ||||
| //  Text edit popup with a title, two text strings, a text input box and buttons
 | ||||
| //  to load the second text string and to clear the input field.
 | ||||
| //  Intended for updating settings for configuration files and similar.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H | ||||
| #define ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H | ||||
| 
 | ||||
| #include "GuiComponent.h" | ||||
| #include "components/ComponentGrid.h" | ||||
| #include "components/NinePatchComponent.h" | ||||
| 
 | ||||
| class TextComponent; | ||||
| class TextEditComponent; | ||||
| 
 | ||||
| class GuiComplexTextEditPopup : public GuiComponent | ||||
| { | ||||
| public: | ||||
|     GuiComplexTextEditPopup(Window* window, | ||||
|                             const HelpStyle& helpstyle, | ||||
|                             const std::string& title, | ||||
|                             const std::string& infoString1, | ||||
|                             const std::string& infoString2, | ||||
|                             const std::string& initValue, | ||||
|                             const std::function<void(const std::string&)>& okCallback, | ||||
|                             bool multiLine, | ||||
|                             const std::string& acceptBtnText = "OK", | ||||
|                             const std::string& saveConfirmationText = "SAVE CHANGES?", | ||||
|                             const std::string& loadBtnText = "LOAD", | ||||
|                             const std::string& loadBtnHelpText = "load default", | ||||
|                             const std::string& clearBtnText = "CLEAR", | ||||
|                             const std::string& clearBtnHelpText = "clear", | ||||
|                             bool hideCancelButton = false); | ||||
| 
 | ||||
|     bool input(InputConfig* config, Input input) override; | ||||
|     void onSizeChanged() override; | ||||
| 
 | ||||
|     std::vector<HelpPrompt> getHelpPrompts() override; | ||||
|     HelpStyle getHelpStyle() override { return mHelpStyle; } | ||||
| 
 | ||||
| private: | ||||
|     NinePatchComponent mBackground; | ||||
|     ComponentGrid mGrid; | ||||
| 
 | ||||
|     std::shared_ptr<TextComponent> mTitle; | ||||
|     std::shared_ptr<TextComponent> mInfoString1; | ||||
|     std::shared_ptr<TextComponent> mInfoString2; | ||||
|     std::shared_ptr<TextEditComponent> mText; | ||||
|     std::shared_ptr<ComponentGrid> mButtonGrid; | ||||
| 
 | ||||
|     HelpStyle mHelpStyle; | ||||
|     bool mMultiLine; | ||||
|     bool mHideCancelButton; | ||||
|     std::string mInitValue; | ||||
|     std::function<void(const std::string&)> mOkCallback; | ||||
|     std::string mSaveConfirmationText; | ||||
| }; | ||||
| 
 | ||||
| #endif // ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H
 | ||||
							
								
								
									
										697
									
								
								es-core/src/guis/GuiTextEditKeyboardPopup.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										697
									
								
								es-core/src/guis/GuiTextEditKeyboardPopup.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,697 @@ | |||
| //  SPDX-License-Identifier: MIT
 | ||||
| //
 | ||||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiTextEditKeyboardPopup.cpp
 | ||||
| //
 | ||||
| //  Text edit popup with a virtual keyboard.
 | ||||
| //  Has a default mode and a complex mode, both with various options passed as arguments.
 | ||||
| //
 | ||||
| 
 | ||||
| #define KEYBOARD_WIDTH Renderer::getScreenWidth() * 0.78f | ||||
| #define KEYBOARD_HEIGHT Renderer::getScreenHeight() * 0.60f | ||||
| 
 | ||||
| #define KEYBOARD_PADDINGX (Renderer::getScreenWidth() * 0.02f) | ||||
| #define KEYBOARD_PADDINGY (Renderer::getScreenWidth() * 0.01f) | ||||
| 
 | ||||
| #define BUTTON_GRID_HORIZ_PADDING (10.0f * Renderer::getScreenHeightModifier()) | ||||
| 
 | ||||
| #define NAVIGATION_REPEAT_START_DELAY 400 | ||||
| #define NAVIGATION_REPEAT_SPEED 70 // Lower is faster.
 | ||||
| 
 | ||||
| #define DELETE_REPEAT_START_DELAY 600 | ||||
| #define DELETE_REPEAT_SPEED 90 // Lower is faster.
 | ||||
| 
 | ||||
| #if defined(_MSC_VER) // MSVC compiler.
 | ||||
| #define DELETE_SYMBOL Utils::String::wideStringToString(L"\uf177") | ||||
| #define OK_SYMBOL Utils::String::wideStringToString(L"\uf058") | ||||
| #define SHIFT_SYMBOL Utils::String::wideStringToString(L"\uf176") | ||||
| #define ALT_SYMBOL Utils::String::wideStringToString(L"\uf141") | ||||
| #else | ||||
| #define DELETE_SYMBOL "\uf177" | ||||
| #define OK_SYMBOL "\uf058" | ||||
| #define SHIFT_SYMBOL "\uf176" | ||||
| #define ALT_SYMBOL "\uf141" | ||||
| #endif | ||||
| 
 | ||||
| #include "guis/GuiTextEditKeyboardPopup.h" | ||||
| 
 | ||||
| #include "components/MenuComponent.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "utils/StringUtil.h" | ||||
| 
 | ||||
| // clang-format off
 | ||||
| std::vector<std::vector<const char*>> kbBaseUS{ | ||||
|     {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"}, | ||||
|     {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"}, | ||||
|     {"¡", "²", "³", "¤", "€", "¼", "½", "¾", "‘", "’", "¥", "×", "DEL"}, | ||||
|     {"¹", "",  "",  "£", "",  "",  "",  "",  "",  "",  "",  "÷", "DEL"}, | ||||
| 
 | ||||
|     {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"}, | ||||
|     {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"}, | ||||
|     {"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"}, | ||||
|     {"Ä", "Å", "É", "",  "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "",  "",  "OK"}, | ||||
| 
 | ||||
|     {"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"}, | ||||
|     {"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"}, | ||||
|     {"á", "ß", "ð", "",  "",  "",  "",  "",  "ø", "¶", "´",  "¬", "-rowspan-"}, | ||||
|     {"Á", "§", "Ð", "",  "",  "",  "",  "",  "Ø", "°", "¨",  "¦", "-rowspan-"}, | ||||
| 
 | ||||
|     {"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"}, | ||||
|     {"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"}, | ||||
|     {"",  "æ", "",  "©", "",  "",  "ñ", "µ", "ç", "",  "¿", "ALT", "-colspan-"}, | ||||
|     {"",  "Æ", "",  "¢", "",  "",  "Ñ", "Μ", "Ç", "",  "",  "ALT", "-colspan-"}}; | ||||
| 
 | ||||
| std::vector<std::vector<const char*>> kbLastRowNormal{ | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; | ||||
| 
 | ||||
| std::vector<std::vector<const char*>> kbLastRowLoad{ | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, | ||||
|     {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; | ||||
| // clang-format on
 | ||||
| 
 | ||||
| GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( | ||||
|     Window* window, | ||||
|     const HelpStyle& helpstyle, | ||||
|     const std::string& title, | ||||
|     const std::string& initValue, | ||||
|     const std::function<void(const std::string&)>& okCallback, | ||||
|     bool multiLine, | ||||
|     const std::string& acceptBtnHelpText, | ||||
|     const std::string& saveConfirmationText, | ||||
|     const std::string& infoString, | ||||
|     const std::string& defaultValue, | ||||
|     const std::string& loadBtnHelpText, | ||||
|     const std::string& clearBtnHelpText, | ||||
|     const std::string& cancelBtnHelpText) | ||||
|     : GuiComponent{window} | ||||
|     , mHelpStyle{helpstyle} | ||||
|     , mInitValue{initValue} | ||||
|     , mOkCallback{okCallback} | ||||
|     , mMultiLine{multiLine} | ||||
|     , mAcceptBtnHelpText{acceptBtnHelpText} | ||||
|     , mSaveConfirmationText{saveConfirmationText} | ||||
|     , mLoadBtnHelpText{loadBtnHelpText} | ||||
|     , mClearBtnHelpText{clearBtnHelpText} | ||||
|     , mCancelBtnHelpText{cancelBtnHelpText} | ||||
|     , mBackground{window, ":/graphics/frame.svg"} | ||||
|     , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}} | ||||
|     , mComplexMode{(infoString != "" && defaultValue != "")} | ||||
|     , mDeleteRepeat{false} | ||||
|     , mShift{false} | ||||
|     , mAlt{false} | ||||
|     , mAltShift{false} | ||||
|     , mDeleteRepeatTimer{0} | ||||
|     , mNavigationRepeatTimer{0} | ||||
|     , mNavigationRepeatDirX{0} | ||||
|     , mNavigationRepeatDirY{0} | ||||
| { | ||||
|     addChild(&mBackground); | ||||
|     addChild(&mGrid); | ||||
| 
 | ||||
|     mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title), | ||||
|                                              Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); | ||||
| 
 | ||||
|     std::vector<std::vector<const char*>> kbLayout; | ||||
| 
 | ||||
|     // At the moment there is only the US keyboard layout available.
 | ||||
|     kbLayout.insert(kbLayout.cend(), kbBaseUS.cbegin(), kbBaseUS.cend()); | ||||
| 
 | ||||
|     // In complex mode, the last row of the keyboard contains an additional "LOAD" button.
 | ||||
|     if (mComplexMode) | ||||
|         kbLayout.insert(kbLayout.cend(), kbLastRowLoad.cbegin(), kbLastRowLoad.cend()); | ||||
|     else | ||||
|         kbLayout.insert(kbLayout.cend(), kbLastRowNormal.cbegin(), kbLastRowNormal.cend()); | ||||
| 
 | ||||
|     mHorizontalKeyCount = static_cast<int>(kbLayout[0].size()); | ||||
| 
 | ||||
|     mKeyboardGrid = std::make_shared<ComponentGrid>( | ||||
|         mWindow, glm::ivec2(mHorizontalKeyCount, static_cast<int>(kbLayout.size()) / 3)); | ||||
| 
 | ||||
|     mText = std::make_shared<TextEditComponent>(mWindow); | ||||
|     mText->setValue(initValue); | ||||
| 
 | ||||
|     if (!multiLine) | ||||
|         mText->setCursor(initValue.size()); | ||||
| 
 | ||||
|     // Header.
 | ||||
|     mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); | ||||
| 
 | ||||
|     int yPos = 1; | ||||
| 
 | ||||
|     if (mComplexMode) { | ||||
|         mInfoString = std::make_shared<TextComponent>( | ||||
|             mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); | ||||
|         mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true); | ||||
| 
 | ||||
|         mDefaultValue = std::make_shared<TextComponent>( | ||||
|             mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); | ||||
|         mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, true); | ||||
|         yPos += 2; | ||||
|     } | ||||
| 
 | ||||
|     // Text edit field.
 | ||||
|     mGrid.setEntry(mText, glm::ivec2{0, yPos}, true, false, glm::ivec2{1, 1}, | ||||
|                    GridFlags::BORDER_TOP); | ||||
| 
 | ||||
|     std::vector<std::vector<std::shared_ptr<ButtonComponent>>> buttonList; | ||||
| 
 | ||||
|     // Create keyboard.
 | ||||
|     for (int i = 0; i < static_cast<int>(kbLayout.size()) / 4; i++) { | ||||
|         std::vector<std::shared_ptr<ButtonComponent>> buttons; | ||||
| 
 | ||||
|         for (int j = 0; j < static_cast<int>(kbLayout[i].size()); j++) { | ||||
|             std::string lower = kbLayout[4 * i][j]; | ||||
|             if (lower.empty() || lower == "-rowspan-" || lower == "-colspan-") | ||||
|                 continue; | ||||
| 
 | ||||
|             std::string upper = kbLayout[4 * i + 1][j]; | ||||
|             std::string alted = kbLayout[4 * i + 2][j]; | ||||
|             std::string altshifted = kbLayout[4 * i + 3][j]; | ||||
| 
 | ||||
|             std::shared_ptr<ButtonComponent> button = nullptr; | ||||
| 
 | ||||
|             if (lower == "DEL") { | ||||
|                 lower = DELETE_SYMBOL; | ||||
|                 upper = DELETE_SYMBOL; | ||||
|                 alted = DELETE_SYMBOL; | ||||
|                 altshifted = DELETE_SYMBOL; | ||||
|             } | ||||
|             else if (lower == "OK") { | ||||
|                 lower = OK_SYMBOL; | ||||
|                 upper = OK_SYMBOL; | ||||
|                 alted = OK_SYMBOL; | ||||
|                 altshifted = OK_SYMBOL; | ||||
|             } | ||||
|             else if (lower == "SPACE") { | ||||
|                 lower = " "; | ||||
|                 upper = " "; | ||||
|                 alted = " "; | ||||
|                 altshifted = " "; | ||||
|             } | ||||
|             else if (lower != "SHIFT" && lower.length() > 1) { | ||||
|                 lower = (lower.c_str()); | ||||
|                 upper = (upper.c_str()); | ||||
|                 alted = (alted.c_str()); | ||||
|                 altshifted = (altshifted.c_str()); | ||||
|             } | ||||
| 
 | ||||
|             if (lower == "SHIFT") { | ||||
|                 mShiftButton = std::make_shared<ButtonComponent>( | ||||
|                     mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true); | ||||
|                 button = mShiftButton; | ||||
|             } | ||||
|             else if (lower == "ALT") { | ||||
|                 mAltButton = std::make_shared<ButtonComponent>( | ||||
|                     mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true); | ||||
|                 button = mAltButton; | ||||
|             } | ||||
|             else { | ||||
|                 button = makeButton(lower, upper, alted, altshifted); | ||||
|             } | ||||
| 
 | ||||
|             button->setPadding( | ||||
|                 glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f, | ||||
|                           BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f)); | ||||
|             buttons.push_back(button); | ||||
| 
 | ||||
|             int colSpan = 1; | ||||
|             for (int cs = j + 1; cs < static_cast<int>(kbLayout[i].size()); cs++) { | ||||
|                 if (std::string(kbLayout[4 * i][cs]) == "-colspan-") | ||||
|                     colSpan++; | ||||
|                 else | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             int rowSpan = 1; | ||||
|             for (int cs = (4 * i) + 4; cs < static_cast<int>(kbLayout.size()); cs += 4) { | ||||
|                 if (std::string(kbLayout[cs][j]) == "-rowspan-") | ||||
|                     rowSpan++; | ||||
|                 else | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             mKeyboardGrid->setEntry(button, glm::ivec2{j, i}, true, true, | ||||
|                                     glm::ivec2{colSpan, rowSpan}); | ||||
| 
 | ||||
|             buttonList.push_back(buttons); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     mGrid.setEntry(mKeyboardGrid, glm::ivec2{0, yPos + 1}, true, true, glm::ivec2{2, 4}); | ||||
| 
 | ||||
|     float textHeight = mText->getFont()->getHeight(); | ||||
|     // If the multiLine option has been set, then include three lines of text on screen.
 | ||||
|     if (multiLine) { | ||||
|         textHeight *= 3.0f; | ||||
|         textHeight += 2.0f * Renderer::getScreenHeightModifier(); | ||||
|     } | ||||
| 
 | ||||
|     mText->setSize(0.0f, textHeight); | ||||
| 
 | ||||
|     // If attempting to navigate beyond the edge of the keyboard grid, then wrap around.
 | ||||
|     mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig* config, Input input) -> bool { | ||||
|         if (config->isMappedLike("left", input)) { | ||||
|             if (mGrid.getSelectedComponent() == mKeyboardGrid) { | ||||
|                 mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1, true); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         else if (config->isMappedLike("right", input)) { | ||||
|             if (mGrid.getSelectedComponent() == mKeyboardGrid) { | ||||
|                 mKeyboardGrid->moveCursorTo(0, -1); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     }); | ||||
| 
 | ||||
|     // Adapt width to the geometry of the display. The 1.778 aspect ratio is the 16:9 reference.
 | ||||
|     float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); | ||||
|     float width = glm::clamp(0.78f * aspectValue, 0.35f, 0.90f) * Renderer::getScreenWidth(); | ||||
| 
 | ||||
|     // The combination of multiLine and complex mode is not supported as there is currently
 | ||||
|     // no need for that.
 | ||||
|     if (mMultiLine) { | ||||
|         setSize(width, KEYBOARD_HEIGHT + textHeight - mText->getFont()->getHeight()); | ||||
| 
 | ||||
|         setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f, | ||||
|                     (static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f); | ||||
|     } | ||||
|     else { | ||||
|         if (mComplexMode) | ||||
|             setSize(width, KEYBOARD_HEIGHT + mDefaultValue->getSize().y * 3.0f); | ||||
|         else | ||||
|             setSize(width, KEYBOARD_HEIGHT); | ||||
| 
 | ||||
|         setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f, | ||||
|                     (static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::onSizeChanged() | ||||
| { | ||||
|     mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); | ||||
|     mText->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX, mText->getSize().y); | ||||
| 
 | ||||
|     // Update grid.
 | ||||
|     mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y); | ||||
| 
 | ||||
|     if (mInfoString && mDefaultValue) { | ||||
|         mGrid.setRowHeightPerc(1, (mInfoString->getSize().y * 0.6f) / mSize.y); | ||||
|         mGrid.setRowHeightPerc(2, (mDefaultValue->getSize().y * 1.6f) / mSize.y); | ||||
|         mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.0f) / mSize.y); | ||||
|     } | ||||
|     else if (mMultiLine) { | ||||
|         mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.15f) / mSize.y); | ||||
|     } | ||||
| 
 | ||||
|     mGrid.setSize(mSize); | ||||
| 
 | ||||
|     auto pos = mKeyboardGrid->getPosition(); | ||||
|     auto sz = mKeyboardGrid->getSize(); | ||||
| 
 | ||||
|     // Add a small margin between buttons.
 | ||||
|     mKeyboardGrid->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX, | ||||
|                            sz.y - KEYBOARD_PADDINGY + 70.0f * Renderer::getScreenHeightModifier()); | ||||
|     mKeyboardGrid->setPosition(KEYBOARD_PADDINGX, pos.y); | ||||
| } | ||||
| 
 | ||||
| bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) | ||||
| { | ||||
|     // Enter/return key or numpad enter key accepts the changes.
 | ||||
|     if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && | ||||
|         input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { | ||||
|         this->mOkCallback(mText->getValue()); | ||||
|         delete this; | ||||
|         return true; | ||||
|     } | ||||
|     // Dito for the A button if using a controller.
 | ||||
|     else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && | ||||
|              config->isMappedTo("a", input) && input.value) { | ||||
|         this->mOkCallback(mText->getValue()); | ||||
|         delete this; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // If the keyboard has been configured with backspace as the back button (which is the default
 | ||||
|     // configuration) then ignore this key if we're currently editing or otherwise it would be
 | ||||
|     // impossible to erase characters using this key.
 | ||||
|     bool keyboardBackspace = (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && | ||||
|                               input.id == SDLK_BACKSPACE); | ||||
| 
 | ||||
|     // Pressing back (or the escape key if using keyboard input) closes us.
 | ||||
|     if ((config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_ESCAPE) || | ||||
|         (!keyboardBackspace && config->isMappedTo("b", input)) && input.value) { | ||||
|         if (mText->getValue() != mInitValue) { | ||||
|             // Changes were made, ask if the user wants to save them.
 | ||||
|             mWindow->pushGui(new GuiMsgBox( | ||||
|                 mWindow, mHelpStyle, mSaveConfirmationText, "YES", | ||||
|                 [this] { | ||||
|                     this->mOkCallback(mText->getValue()); | ||||
|                     delete this; | ||||
|                     return true; | ||||
|                 }, | ||||
|                 "NO", | ||||
|                 [this] { | ||||
|                     delete this; | ||||
|                     return false; | ||||
|                 })); | ||||
|         } | ||||
|         else { | ||||
|             delete this; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (mText->isEditing() && config->isMappedLike("down", input) && input.value) { | ||||
|         mText->stopEditing(); | ||||
|         mGrid.setCursorTo(mGrid.getSelectedComponent()); | ||||
|     } | ||||
| 
 | ||||
|     // Left trigger button outside text editing field toggles Shift key.
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("lefttrigger", input) && input.value) | ||||
|         shiftKeys(); | ||||
| 
 | ||||
|     // Right trigger button outside text editing field toggles Alt key.
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("righttrigger", input) && input.value) | ||||
|         altKeys(); | ||||
| 
 | ||||
|     // Left shoulder button deletes a character (backspace).
 | ||||
|     if (config->isMappedTo("leftshoulder", input)) { | ||||
|         if (input.value) { | ||||
|             mDeleteRepeat = true; | ||||
|             mDeleteRepeatTimer = -(DELETE_REPEAT_START_DELAY - DELETE_REPEAT_SPEED); | ||||
| 
 | ||||
|             bool editing = mText->isEditing(); | ||||
|             if (!editing) | ||||
|                 mText->startEditing(); | ||||
| 
 | ||||
|             mText->textInput("\b"); | ||||
| 
 | ||||
|             if (!editing) | ||||
|                 mText->stopEditing(); | ||||
|         } | ||||
|         else { | ||||
|             mDeleteRepeat = false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Right shoulder button inserts a blank space.
 | ||||
|     if (config->isMappedTo("rightshoulder", input) && input.value) { | ||||
|         bool editing = mText->isEditing(); | ||||
|         if (!editing) | ||||
|             mText->startEditing(); | ||||
| 
 | ||||
|         mText->textInput(" "); | ||||
| 
 | ||||
|         if (!editing) | ||||
|             mText->stopEditing(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Actual navigation of the keyboard grid is done in ComponentGrid, this code only handles
 | ||||
|     // key repeat while holding the left/right/up/down buttons.
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("left", input)) { | ||||
|         if (input.value) { | ||||
|             mNavigationRepeatDirX = -1; | ||||
|             mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); | ||||
|         } | ||||
|         else { | ||||
|             mNavigationRepeatDirX = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("right", input)) { | ||||
|         if (input.value) { | ||||
|             mNavigationRepeatDirX = 1; | ||||
|             mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); | ||||
|         } | ||||
|         else { | ||||
|             mNavigationRepeatDirX = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("up", input)) { | ||||
|         if (input.value) { | ||||
|             mNavigationRepeatDirY = -1; | ||||
|             mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); | ||||
|         } | ||||
|         else { | ||||
|             mNavigationRepeatDirY = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (!mText->isEditing() && config->isMappedLike("down", input)) { | ||||
|         if (input.value) { | ||||
|             mNavigationRepeatDirY = 1; | ||||
|             mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); | ||||
|         } | ||||
|         else { | ||||
|             mNavigationRepeatDirY = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (GuiComponent::input(config, input)) | ||||
|         return true; | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::update(int deltaTime) | ||||
| { | ||||
|     updateNavigationRepeat(deltaTime); | ||||
|     updateDeleteRepeat(deltaTime); | ||||
|     GuiComponent::update(deltaTime); | ||||
| } | ||||
| 
 | ||||
| std::vector<HelpPrompt> GuiTextEditKeyboardPopup::getHelpPrompts() | ||||
| { | ||||
|     std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts(); | ||||
| 
 | ||||
|     if (!mText->isEditing()) { | ||||
|         prompts.push_back(HelpPrompt("lt", "shift")); | ||||
|         prompts.push_back(HelpPrompt("rt", "alt")); | ||||
|     } | ||||
|     else { | ||||
|         prompts.push_back(HelpPrompt("a", mAcceptBtnHelpText)); | ||||
|     } | ||||
| 
 | ||||
|     prompts.push_back(HelpPrompt("l", "backspace")); | ||||
|     prompts.push_back(HelpPrompt("r", "space")); | ||||
|     prompts.push_back(HelpPrompt("b", "back")); | ||||
| 
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == OK_SYMBOL) | ||||
|         prompts.front().second = mAcceptBtnHelpText; | ||||
| 
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == " ") | ||||
|         prompts.front().second = "SPACE"; | ||||
| 
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == "CLEAR") | ||||
|         prompts.front().second = mClearBtnHelpText; | ||||
| 
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == "LOAD") | ||||
|         prompts.front().second = mLoadBtnHelpText; | ||||
| 
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == "CANCEL") | ||||
|         prompts.front().second = mCancelBtnHelpText; | ||||
| 
 | ||||
|     // If a prompt has no value set, then remove it.
 | ||||
|     if (prompts.size() > 0 && prompts.front().second == "") | ||||
|         prompts.erase(prompts.begin(), prompts.begin() + 1); | ||||
| 
 | ||||
|     return prompts; | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) | ||||
| { | ||||
|     if (!mDeleteRepeat) | ||||
|         return; | ||||
| 
 | ||||
|     mDeleteRepeatTimer += deltaTime; | ||||
| 
 | ||||
|     while (mDeleteRepeatTimer >= DELETE_REPEAT_SPEED) { | ||||
|         bool editing = mText->isEditing(); | ||||
|         if (!editing) | ||||
|             mText->startEditing(); | ||||
| 
 | ||||
|         mText->textInput("\b"); | ||||
| 
 | ||||
|         if (!editing) | ||||
|             mText->stopEditing(); | ||||
| 
 | ||||
|         mDeleteRepeatTimer -= DELETE_REPEAT_SPEED; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) | ||||
| { | ||||
|     if (mNavigationRepeatDirX == 0 && mNavigationRepeatDirY == 0) | ||||
|         return; | ||||
| 
 | ||||
|     mNavigationRepeatTimer += deltaTime; | ||||
| 
 | ||||
|     while (mNavigationRepeatTimer >= NAVIGATION_REPEAT_SPEED) { | ||||
| 
 | ||||
|         if (mNavigationRepeatDirX != 0) { | ||||
|             mKeyboardGrid.get()->moveCursor({mNavigationRepeatDirX, 0}); | ||||
|             // If replacing the line above with this code, the keyboard will wrap around the
 | ||||
|             // edges also when key repeat is active.
 | ||||
|             //            if (!mKeyboardGrid.get()->moveCursor({mNavigationRepeatDirX, 0})) {
 | ||||
|             //                if (mNavigationRepeatDirX < 0)
 | ||||
|             //                    mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1);
 | ||||
|             //                else
 | ||||
|             //                    mKeyboardGrid->moveCursorTo(0, -1);
 | ||||
|             //            }
 | ||||
|         } | ||||
| 
 | ||||
|         if (mNavigationRepeatDirY != 0) | ||||
|             mKeyboardGrid.get()->moveCursor({0, mNavigationRepeatDirY}); | ||||
| 
 | ||||
|         mNavigationRepeatTimer -= NAVIGATION_REPEAT_SPEED; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::shiftKeys() | ||||
| { | ||||
|     mShift = !mShift; | ||||
| 
 | ||||
|     if (mShift) { | ||||
|         mShiftButton->setFlatColorFocused(0xFF2222FF); | ||||
|         mShiftButton->setFlatColorUnfocused(0xFF2222FF); | ||||
|     } | ||||
|     else { | ||||
|         mShiftButton->setFlatColorFocused(0x878787FF); | ||||
|         mShiftButton->setFlatColorUnfocused(0x60606025); | ||||
|     } | ||||
| 
 | ||||
|     if (mAlt && mShift) { | ||||
|         altShiftKeys(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // This only happens when Alt was deselected while both Shift and Alt were active.
 | ||||
|     if (mAlt) { | ||||
|         altKeys(); | ||||
|         altKeys(); | ||||
|     } | ||||
|     else { | ||||
|         for (auto& kb : mKeyboardButtons) { | ||||
|             const std::string& text = mShift ? kb.shiftedKey : kb.key; | ||||
|             auto sz = kb.button->getSize(); | ||||
|             kb.button->setText(text, text, false); | ||||
|             kb.button->setSize(sz); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::altKeys() | ||||
| { | ||||
|     mAlt = !mAlt; | ||||
| 
 | ||||
|     if (mAlt) { | ||||
|         mAltButton->setFlatColorFocused(0xFF2222FF); | ||||
|         mAltButton->setFlatColorUnfocused(0xFF2222FF); | ||||
|     } | ||||
|     else { | ||||
|         mAltButton->setFlatColorFocused(0x878787FF); | ||||
|         mAltButton->setFlatColorUnfocused(0x60606025); | ||||
|     } | ||||
| 
 | ||||
|     if (mShift && mAlt) { | ||||
|         altShiftKeys(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // This only happens when Shift was deselected while both Shift and Alt were active.
 | ||||
|     if (mShift) { | ||||
|         shiftKeys(); | ||||
|         shiftKeys(); | ||||
|     } | ||||
|     else { | ||||
|         for (auto& kb : mKeyboardButtons) { | ||||
|             const std::string& text = mAlt ? kb.altedKey : kb.key; | ||||
|             auto sz = kb.button->getSize(); | ||||
|             kb.button->setText(text, text, false); | ||||
|             kb.button->setSize(sz); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditKeyboardPopup::altShiftKeys() | ||||
| { | ||||
|     for (auto& kb : mKeyboardButtons) { | ||||
|         const std::string& text = kb.altshiftedKey; | ||||
|         auto sz = kb.button->getSize(); | ||||
|         kb.button->setText(text, text, false); | ||||
|         kb.button->setSize(sz); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<ButtonComponent> GuiTextEditKeyboardPopup::makeButton( | ||||
|     const std::string& key, | ||||
|     const std::string& shiftedKey, | ||||
|     const std::string& altedKey, | ||||
|     const std::string& altshiftedKey) | ||||
| { | ||||
|     std::shared_ptr<ButtonComponent> button = std::make_shared<ButtonComponent>( | ||||
|         mWindow, key, key, | ||||
|         [this, key, shiftedKey, altedKey, altshiftedKey] { | ||||
|             if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) { | ||||
|                 mOkCallback(mText->getValue()); | ||||
|                 delete this; | ||||
|                 return; | ||||
|             } | ||||
|             else if (key == (DELETE_SYMBOL) || key == "DEL") { | ||||
|                 mText->startEditing(); | ||||
|                 mText->textInput("\b"); | ||||
|                 mText->stopEditing(); | ||||
|                 return; | ||||
|             } | ||||
|             else if (key == "SPACE" || key == " ") { | ||||
|                 mText->startEditing(); | ||||
|                 mText->textInput(" "); | ||||
|                 mText->stopEditing(); | ||||
|                 return; | ||||
|             } | ||||
|             else if (key == "LOAD") { | ||||
|                 mText->setValue(mDefaultValue->getValue()); | ||||
|                 mText->setCursor(mDefaultValue->getValue().size()); | ||||
|                 return; | ||||
|             } | ||||
|             else if (key == "CLEAR") { | ||||
|                 mText->setValue(""); | ||||
|                 return; | ||||
|             } | ||||
|             else if (key == "CANCEL") { | ||||
|                 delete this; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (mAlt && altedKey.empty()) | ||||
|                 return; | ||||
| 
 | ||||
|             mText->startEditing(); | ||||
| 
 | ||||
|             if (mShift && mAlt) | ||||
|                 mText->textInput(altshiftedKey.c_str()); | ||||
|             else if (mAlt) | ||||
|                 mText->textInput(altedKey.c_str()); | ||||
|             else if (mShift) | ||||
|                 mText->textInput(shiftedKey.c_str()); | ||||
|             else | ||||
|                 mText->textInput(key.c_str()); | ||||
| 
 | ||||
|             mText->stopEditing(); | ||||
|         }, | ||||
|         false, true); | ||||
| 
 | ||||
|     KeyboardButton kb(button, key, shiftedKey, altedKey, altshiftedKey); | ||||
|     mKeyboardButtons.push_back(kb); | ||||
|     return button; | ||||
| } | ||||
							
								
								
									
										112
									
								
								es-core/src/guis/GuiTextEditKeyboardPopup.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								es-core/src/guis/GuiTextEditKeyboardPopup.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| //  SPDX-License-Identifier: MIT
 | ||||
| //
 | ||||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiTextEditKeyboardPopup.h
 | ||||
| //
 | ||||
| //  Text edit popup with a virtual keyboard.
 | ||||
| //  Has a default mode and a complex mode, both with various options passed as arguments.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H | ||||
| #define ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H | ||||
| 
 | ||||
| #include "GuiComponent.h" | ||||
| #include "components/ButtonComponent.h" | ||||
| #include "components/ComponentGrid.h" | ||||
| #include "components/TextEditComponent.h" | ||||
| 
 | ||||
| class GuiTextEditKeyboardPopup : public GuiComponent | ||||
| { | ||||
| public: | ||||
|     GuiTextEditKeyboardPopup(Window* window, | ||||
|                              const HelpStyle& helpstyle, | ||||
|                              const std::string& title, | ||||
|                              const std::string& initValue, | ||||
|                              const std::function<void(const std::string&)>& okCallback, | ||||
|                              bool multiLine, | ||||
|                              const std::string& acceptBtnHelpText = "OK", | ||||
|                              const std::string& saveConfirmationText = "SAVE CHANGES?", | ||||
|                              const std::string& infoString = "", | ||||
|                              const std::string& defaultValue = "", | ||||
|                              const std::string& loadBtnHelpText = "LOAD DEFAULT", | ||||
|                              const std::string& clearBtnHelpText = "CLEAR", | ||||
|                              const std::string& cancelBtnHelpText = "DISCARD CHANGES"); | ||||
| 
 | ||||
|     void onSizeChanged(); | ||||
|     bool input(InputConfig* config, Input input); | ||||
|     void update(int deltaTime) override; | ||||
| 
 | ||||
|     std::vector<HelpPrompt> getHelpPrompts() override; | ||||
|     HelpStyle getHelpStyle() override { return mHelpStyle; } | ||||
| 
 | ||||
| private: | ||||
|     class KeyboardButton | ||||
|     { | ||||
|     public: | ||||
|         std::shared_ptr<ButtonComponent> button; | ||||
|         const std::string key; | ||||
|         const std::string shiftedKey; | ||||
|         const std::string altedKey; | ||||
|         const std::string altshiftedKey; | ||||
|         KeyboardButton(const std::shared_ptr<ButtonComponent> b, | ||||
|                        const std::string& k, | ||||
|                        const std::string& sk, | ||||
|                        const std::string& ak, | ||||
|                        const std::string& ask) | ||||
|             : button{b} | ||||
|             , key{k} | ||||
|             , shiftedKey{sk} | ||||
|             , altedKey{ak} | ||||
|             , altshiftedKey{ask} {}; | ||||
|     }; | ||||
| 
 | ||||
|     void updateDeleteRepeat(int deltaTime); | ||||
|     void updateNavigationRepeat(int deltaTime); | ||||
| 
 | ||||
|     void shiftKeys(); | ||||
|     void altKeys(); | ||||
|     void altShiftKeys(); | ||||
| 
 | ||||
|     std::shared_ptr<ButtonComponent> makeButton(const std::string& key, | ||||
|                                                 const std::string& shiftedKey, | ||||
|                                                 const std::string& altedKey, | ||||
|                                                 const std::string& altshiftedKey); | ||||
|     std::vector<KeyboardButton> mKeyboardButtons; | ||||
| 
 | ||||
|     std::shared_ptr<ButtonComponent> mShiftButton; | ||||
|     std::shared_ptr<ButtonComponent> mAltButton; | ||||
| 
 | ||||
|     NinePatchComponent mBackground; | ||||
|     ComponentGrid mGrid; | ||||
|     HelpStyle mHelpStyle; | ||||
| 
 | ||||
|     std::shared_ptr<TextComponent> mTitle; | ||||
|     std::shared_ptr<TextComponent> mInfoString; | ||||
|     std::shared_ptr<TextComponent> mDefaultValue; | ||||
|     std::shared_ptr<TextEditComponent> mText; | ||||
|     std::shared_ptr<ComponentGrid> mKeyboardGrid; | ||||
| 
 | ||||
|     std::string mInitValue; | ||||
|     std::string mAcceptBtnHelpText; | ||||
|     std::string mSaveConfirmationText; | ||||
|     std::string mLoadBtnHelpText; | ||||
|     std::string mClearBtnHelpText; | ||||
|     std::string mCancelBtnHelpText; | ||||
| 
 | ||||
|     std::function<void(const std::string&)> mOkCallback; | ||||
| 
 | ||||
|     bool mMultiLine; | ||||
|     bool mComplexMode; | ||||
|     bool mDeleteRepeat; | ||||
|     bool mShift; | ||||
|     bool mAlt; | ||||
|     bool mAltShift; | ||||
| 
 | ||||
|     int mHorizontalKeyCount; | ||||
|     int mDeleteRepeatTimer; | ||||
|     int mNavigationRepeatTimer; | ||||
|     int mNavigationRepeatDirX; | ||||
|     int mNavigationRepeatDirY; | ||||
| }; | ||||
| 
 | ||||
| #endif // ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H
 | ||||
|  | @ -3,15 +3,16 @@ | |||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiTextEditPopup.cpp
 | ||||
| //
 | ||||
| //  Simple text edit popup with a title, a text input box and OK and Cancel buttons.
 | ||||
| //  Text edit popup.
 | ||||
| //  Has a default mode and a complex mode, both with various options passed as arguments.
 | ||||
| //
 | ||||
| 
 | ||||
| #define DELETE_REPEAT_START_DELAY 600 | ||||
| #define DELETE_REPEAT_SPEED 90 // Lower is faster.
 | ||||
| 
 | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| 
 | ||||
| #include "Window.h" | ||||
| #include "components/ButtonComponent.h" | ||||
| #include "components/MenuComponent.h" | ||||
| #include "components/TextEditComponent.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| 
 | ||||
| GuiTextEditPopup::GuiTextEditPopup(Window* window, | ||||
|  | @ -21,15 +22,27 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, | |||
|                                    const std::function<void(const std::string&)>& okCallback, | ||||
|                                    bool multiLine, | ||||
|                                    const std::string& acceptBtnText, | ||||
|                                    const std::string& saveConfirmationText) | ||||
|     : GuiComponent(window) | ||||
|     , mHelpStyle(helpstyle) | ||||
|     , mBackground(window, ":/graphics/frame.svg") | ||||
|     , mGrid(window, glm::ivec2{1, 3}) | ||||
|     , mMultiLine(multiLine) | ||||
|     , mInitValue(initValue) | ||||
|     , mOkCallback(okCallback) | ||||
|     , mSaveConfirmationText(saveConfirmationText) | ||||
|                                    const std::string& saveConfirmationText, | ||||
|                                    const std::string& infoString, | ||||
|                                    const std::string& defaultValue, | ||||
|                                    const std::string& loadBtnHelpText, | ||||
|                                    const std::string& clearBtnHelpText, | ||||
|                                    const std::string& cancelBtnHelpText) | ||||
|     : GuiComponent{window} | ||||
|     , mHelpStyle{helpstyle} | ||||
|     , mInitValue{initValue} | ||||
|     , mOkCallback{okCallback} | ||||
|     , mMultiLine{multiLine} | ||||
|     , mAcceptBtnText{acceptBtnText} | ||||
|     , mSaveConfirmationText{saveConfirmationText} | ||||
|     , mLoadBtnHelpText{loadBtnHelpText} | ||||
|     , mClearBtnHelpText{clearBtnHelpText} | ||||
|     , mCancelBtnHelpText{cancelBtnHelpText} | ||||
|     , mBackground{window, ":/graphics/frame.svg"} | ||||
|     , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}} | ||||
|     , mComplexMode{(infoString != "" && defaultValue != "")} | ||||
|     , mDeleteRepeat{false} | ||||
|     , mDeleteRepeatTimer{0} | ||||
| { | ||||
|     addChild(&mBackground); | ||||
|     addChild(&mGrid); | ||||
|  | @ -37,6 +50,13 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, | |||
|     mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title), | ||||
|                                              Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); | ||||
| 
 | ||||
|     if (mComplexMode) { | ||||
|         mInfoString = std::make_shared<TextComponent>( | ||||
|             mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); | ||||
|         mDefaultValue = std::make_shared<TextComponent>( | ||||
|             mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); | ||||
|     } | ||||
| 
 | ||||
|     mText = std::make_shared<TextEditComponent>(mWindow); | ||||
|     mText->setValue(initValue); | ||||
| 
 | ||||
|  | @ -46,55 +66,116 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, | |||
|                                                             okCallback(mText->getValue()); | ||||
|                                                             delete this; | ||||
|                                                         })); | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear", | ||||
|     if (mComplexMode) { | ||||
|         buttons.push_back(std::make_shared<ButtonComponent>( | ||||
|             mWindow, "load", loadBtnHelpText, [this, defaultValue] { | ||||
|                 mText->setValue(defaultValue); | ||||
|                 mText->setCursor(0); | ||||
|                 mText->setCursor(defaultValue.size()); | ||||
|             })); | ||||
|     } | ||||
| 
 | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "clear", clearBtnHelpText, | ||||
|                                                         [this] { mText->setValue(""); })); | ||||
| 
 | ||||
|     buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes", | ||||
|                                                         [this] { delete this; })); | ||||
| 
 | ||||
|     mButtonGrid = makeButtonGrid(mWindow, buttons); | ||||
| 
 | ||||
|     mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); | ||||
|     mGrid.setEntry(mText, glm::ivec2{0, 1}, true, false, glm::ivec2{1, 1}, | ||||
| 
 | ||||
|     int yPos = 1; | ||||
| 
 | ||||
|     if (mComplexMode) { | ||||
|         mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true); | ||||
|         mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, false); | ||||
|         yPos += 2; | ||||
|     } | ||||
| 
 | ||||
|     mGrid.setEntry(mText, glm::ivec2{0, yPos}, true, false, glm::ivec2{1, 1}, | ||||
|                    GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); | ||||
|     mGrid.setEntry(mButtonGrid, glm::ivec2{0, 2}, true, false); | ||||
|     mGrid.setEntry(mButtonGrid, glm::ivec2{0, yPos + 1}, true, false); | ||||
| 
 | ||||
|     float textHeight = mText->getFont()->getHeight(); | ||||
| 
 | ||||
|     if (multiLine) | ||||
|         textHeight *= 6.0f; | ||||
| 
 | ||||
|     mText->setSize(0, textHeight); | ||||
| 
 | ||||
|     // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent
 | ||||
|     // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
 | ||||
|     // Adapt width to the geometry of the display. The 1.778 aspect ratio is the 16:9 reference.
 | ||||
|     float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); | ||||
|     float width = glm::clamp(0.50f * aspectValue, 0.40f, 0.70f) * Renderer::getScreenWidth(); | ||||
| 
 | ||||
|     setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + | ||||
|                        mButtonGrid->getSize().y / 2.0f); | ||||
|     setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, | ||||
|                 (Renderer::getScreenHeight() - mSize.y) / 2.0f); | ||||
|     if (mComplexMode) { | ||||
|         float infoWidth = | ||||
|             glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth(); | ||||
|         float windowWidth = | ||||
|             glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth(); | ||||
| 
 | ||||
|         mDefaultValue->setSize(infoWidth, mDefaultValue->getFont()->getHeight()); | ||||
| 
 | ||||
|         setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight + | ||||
|                                  mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f); | ||||
|         setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, | ||||
|                     (Renderer::getScreenHeight() - mSize.y) / 2.0f); | ||||
|     } | ||||
|     else { | ||||
|         float width = glm::clamp(0.54f * aspectValue, 0.20f, 0.70f) * Renderer::getScreenWidth(); | ||||
| 
 | ||||
|         setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + | ||||
|                            mButtonGrid->getSize().y / 2.0f); | ||||
|         setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, | ||||
|                     (Renderer::getScreenHeight() - mSize.y) / 2.0f); | ||||
|     } | ||||
| 
 | ||||
|     if (!multiLine) | ||||
|         mText->setCursor(initValue.size()); | ||||
| 
 | ||||
|     mText->startEditing(); | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditPopup::onSizeChanged() | ||||
| { | ||||
|     mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); | ||||
| 
 | ||||
|     mText->setSize(mSize.x - 40.0f, mText->getSize().y); | ||||
|     mText->setSize(mSize.x - 40.0f * Renderer::getScreenHeightModifier(), mText->getSize().y); | ||||
| 
 | ||||
|     // Update grid.
 | ||||
|     mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y); | ||||
| 
 | ||||
|     if (mComplexMode) | ||||
|         mGrid.setRowHeightPerc(1, 0.15f); | ||||
| 
 | ||||
|     mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y / mSize.y); | ||||
|     mGrid.setSize(mSize); | ||||
| } | ||||
| 
 | ||||
| bool GuiTextEditPopup::input(InputConfig* config, Input input) | ||||
| { | ||||
|     if (GuiComponent::input(config, input)) | ||||
|     // Enter key (main key or via numpad) accepts the changes.
 | ||||
|     if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && | ||||
|         input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { | ||||
|         this->mOkCallback(mText->getValue()); | ||||
|         delete this; | ||||
|         return true; | ||||
|     } | ||||
|     // Dito for the A button if using a controller.
 | ||||
|     else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && | ||||
|              config->isMappedTo("a", input) && input.value) { | ||||
|         this->mOkCallback(mText->getValue()); | ||||
|         delete this; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Pressing back when not text editing closes us.
 | ||||
|     if (config->isMappedTo("b", input) && input.value) { | ||||
|     // If the keyboard has been configured with backspace as the back button (which is the default
 | ||||
|     // configuration) then ignore this key if we're currently editing or otherwise it would be
 | ||||
|     // impossible to erase characters using this key.
 | ||||
|     bool keyboardBackspace = (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && | ||||
|                               input.id == SDLK_BACKSPACE); | ||||
| 
 | ||||
|     // Pressing back (or the escape key if using keyboard input) closes us.
 | ||||
|     if ((config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_ESCAPE) || | ||||
|         (!keyboardBackspace && config->isMappedTo("b", input)) && input.value) { | ||||
|         if (mText->getValue() != mInitValue) { | ||||
|             // Changes were made, ask if the user wants to save them.
 | ||||
|             mWindow->pushGui(new GuiMsgBox( | ||||
|  | @ -114,12 +195,89 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input) | |||
|             delete this; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (mText->isEditing() && config->isMappedLike("down", input) && input.value) { | ||||
|         mText->stopEditing(); | ||||
|         mGrid.setCursorTo(mGrid.getSelectedComponent()); | ||||
|     } | ||||
| 
 | ||||
|     // Left shoulder button deletes a character (backspace).
 | ||||
|     if (config->isMappedTo("leftshoulder", input)) { | ||||
|         if (input.value) { | ||||
|             mDeleteRepeat = true; | ||||
|             mDeleteRepeatTimer = -(DELETE_REPEAT_START_DELAY - DELETE_REPEAT_SPEED); | ||||
| 
 | ||||
|             bool editing = mText->isEditing(); | ||||
|             if (!editing) | ||||
|                 mText->startEditing(); | ||||
| 
 | ||||
|             mText->textInput("\b"); | ||||
| 
 | ||||
|             if (!editing) | ||||
|                 mText->stopEditing(); | ||||
|         } | ||||
|         else { | ||||
|             mDeleteRepeat = false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Right shoulder button inserts a blank space.
 | ||||
|     if (config->isMappedTo("rightshoulder", input) && input.value) { | ||||
|         bool editing = mText->isEditing(); | ||||
|         if (!editing) | ||||
|             mText->startEditing(); | ||||
| 
 | ||||
|         mText->textInput(" "); | ||||
| 
 | ||||
|         if (!editing) | ||||
|             mText->stopEditing(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if (GuiComponent::input(config, input)) | ||||
|         return true; | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditPopup::update(int deltaTime) | ||||
| { | ||||
|     updateDeleteRepeat(deltaTime); | ||||
|     GuiComponent::update(deltaTime); | ||||
| } | ||||
| 
 | ||||
| std::vector<HelpPrompt> GuiTextEditPopup::getHelpPrompts() | ||||
| { | ||||
|     std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts(); | ||||
| 
 | ||||
|     if (mText->isEditing()) | ||||
|         prompts.push_back(HelpPrompt("a", mAcceptBtnText)); | ||||
| 
 | ||||
|     prompts.push_back(HelpPrompt("l", "backspace")); | ||||
|     prompts.push_back(HelpPrompt("r", "space")); | ||||
|     prompts.push_back(HelpPrompt("b", "back")); | ||||
|     return prompts; | ||||
| } | ||||
| 
 | ||||
| void GuiTextEditPopup::updateDeleteRepeat(int deltaTime) | ||||
| { | ||||
|     if (!mDeleteRepeat) | ||||
|         return; | ||||
| 
 | ||||
|     mDeleteRepeatTimer += deltaTime; | ||||
| 
 | ||||
|     while (mDeleteRepeatTimer >= DELETE_REPEAT_SPEED) { | ||||
|         bool editing = mText->isEditing(); | ||||
|         if (!editing) | ||||
|             mText->startEditing(); | ||||
| 
 | ||||
|         mText->textInput("\b"); | ||||
| 
 | ||||
|         if (!editing) | ||||
|             mText->stopEditing(); | ||||
| 
 | ||||
|         mDeleteRepeatTimer -= DELETE_REPEAT_SPEED; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -3,18 +3,17 @@ | |||
| //  EmulationStation Desktop Edition
 | ||||
| //  GuiTextEditPopup.h
 | ||||
| //
 | ||||
| //  Simple text edit popup with a title, a text input box and OK and Cancel buttons.
 | ||||
| //  Text edit popup.
 | ||||
| //  Has a default mode and a complex mode, both with various options passed as arguments.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H | ||||
| #define ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H | ||||
| 
 | ||||
| #include "GuiComponent.h" | ||||
| #include "components/ButtonComponent.h" | ||||
| #include "components/ComponentGrid.h" | ||||
| #include "components/NinePatchComponent.h" | ||||
| 
 | ||||
| class TextComponent; | ||||
| class TextEditComponent; | ||||
| #include "components/TextEditComponent.h" | ||||
| 
 | ||||
| class GuiTextEditPopup : public GuiComponent | ||||
| { | ||||
|  | @ -26,27 +25,47 @@ public: | |||
|                      const std::function<void(const std::string&)>& okCallback, | ||||
|                      bool multiLine, | ||||
|                      const std::string& acceptBtnText = "OK", | ||||
|                      const std::string& saveConfirmationText = "SAVE CHANGES?"); | ||||
|                      const std::string& saveConfirmationText = "SAVE CHANGES?", | ||||
|                      const std::string& infoString = "", | ||||
|                      const std::string& defaultValue = "", | ||||
|                      const std::string& loadBtnHelpText = "LOAD DEFAULT", | ||||
|                      const std::string& clearBtnHelpText = "CLEAR", | ||||
|                      const std::string& cancelBtnHelpText = "DISCARD CHANGES"); | ||||
| 
 | ||||
|     bool input(InputConfig* config, Input input) override; | ||||
|     void onSizeChanged() override; | ||||
|     bool input(InputConfig* config, Input input) override; | ||||
|     void update(int deltaTime) override; | ||||
| 
 | ||||
|     std::vector<HelpPrompt> getHelpPrompts() override; | ||||
|     HelpStyle getHelpStyle() override { return mHelpStyle; } | ||||
| 
 | ||||
| private: | ||||
|     void updateDeleteRepeat(int deltaTime); | ||||
| 
 | ||||
|     NinePatchComponent mBackground; | ||||
|     ComponentGrid mGrid; | ||||
|     HelpStyle mHelpStyle; | ||||
| 
 | ||||
|     std::shared_ptr<TextComponent> mTitle; | ||||
|     std::shared_ptr<TextComponent> mInfoString; | ||||
|     std::shared_ptr<TextComponent> mDefaultValue; | ||||
|     std::shared_ptr<TextEditComponent> mText; | ||||
|     std::shared_ptr<ComponentGrid> mButtonGrid; | ||||
| 
 | ||||
|     HelpStyle mHelpStyle; | ||||
|     bool mMultiLine; | ||||
|     std::string mInitValue; | ||||
|     std::function<void(const std::string&)> mOkCallback; | ||||
|     std::string mAcceptBtnText; | ||||
|     std::string mSaveConfirmationText; | ||||
|     std::string mLoadBtnHelpText; | ||||
|     std::string mClearBtnHelpText; | ||||
|     std::string mCancelBtnHelpText; | ||||
| 
 | ||||
|     std::function<void(const std::string&)> mOkCallback; | ||||
| 
 | ||||
|     bool mMultiLine; | ||||
|     bool mComplexMode; | ||||
|     bool mDeleteRepeat; | ||||
| 
 | ||||
|     int mDeleteRepeatTimer; | ||||
| }; | ||||
| 
 | ||||
| #endif // ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Leon Styhre
						Leon Styhre