diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a83df73..f21da3ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting * Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen +* Improved the layout of the scraper GUIs (single-game scraper and multi-scraper) +* Added horizontal scrolling of long game names to the scraper GUIs * Improved the gamelist filter screen to not allow filtering of values where there is no actual data to filter, e.g. Favorites for a system with no favorite games * Grayed out all fields in the gamelist filter screen where there is no data to filter, previously some fields were removed entirely and some could still be used * Added the ability to filter on blank/unknown values for Genre, Player, Developer, Publisher and Alternative emulator. @@ -25,18 +27,22 @@ * Lowered the minimum supported screen resolution from 640x480 to 224x224 to support arcade cabinet displays such as those running at 384x224 and 224x384 * Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, upper/lower/camel case and custom spacing * Made the scrolling speed of ScrollableContainer more consistent across various screen resolutions and display aspect ratios +* Decreased the amount of text that ScrollableContainer renders above and below the starting position as content is scrolled * Made the game name and description stop scrolling when running the media viewer, the screensaver or when running in the background while a game is launched * Added notification popups when plugging in or removing controllers * Changed to loading the default theme set rbsimple-DE instead of the first available theme if the currently configured theme is missing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view +* Replaced a number of help prompt hacks with proper solutions * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" +* Changed the font size for the custom collection deletion screen to the same size as for all other menus * Added support for upscaling bitmap images using linear filtering * Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist views * Moved the Media Viewer and Screensaver settings higher in the UI Settings menu * Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators entry * Added a blinking cursor to TextEditComponent * Changed the filter description "Text filter (game name)" to "Game name" +* Removed a margin hack from TextComponent and if abbreviated strings end with a space character, that space is now removed * Added support for multi-select total count and exclusive multi-select to OptionListComponent * Added support for a maximum name length to OptionListComponent (non-multiselect only) with an abbreviation of the name if it exceeds this value * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button @@ -70,6 +76,7 @@ * When scraping in interactive mode, the game counter was not decreased when skipping games, making it impossible to skip the final games in the queue * When scraping in interactive mode, "No games found" results could be accepted using the "A" button * When scraping in interactive mode, any refining done using the "Y" button shortcut would not be shown when doing another refine using the "Refine search" button +* Fixed multiple minor rendering issues where graphics would be slightly cut off or incorrectly resized * Under some circumstances ScrollableContainer (used for the game descriptions) would contain a partially rendered bottom line * If the TextListComponent height was not evenly dividable by the font height + line spacing, a partial bottom row would get rendered * The line spacing for TextListComponent was incorrectly calculated for some resolutions such as 2560x1440 @@ -86,6 +93,7 @@ * Under some circumstances and at some screen resolutions, the last menu separator line would not get rendered (still an issue at extreme resolutions like 320x240) * When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior * With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size +* The custom collection deletion screen had incorrect row heights when running at lower resolutions such as 1280x720 * If there was an abbreviated full system name for the "Gamelist on startup" option, that abbreviation would also get displayed when opening the selector window * Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe83c2b6b..b37752bfe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,7 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.4 * Localization/multi-language support +* Reorganize the menus, possibly adding basic/advanced modes * Authoring tools to clean up orphaned gamelist entries, media files etc. * Scrollbar component for the gamelist view which can be used by the themes * Web proxy support for the scraper diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index c0117dc04..6c944394a 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -47,6 +47,8 @@ The following operating systems have been tested (all for the x86 architecture u **Note:** If using a Mac with an ARM CPU (e.g. M1) you need to install the x86 version of RetroArch and any other emulators, or you won't be able to launch any games. This will be fixed whenever a native macOS ARM build of ES-DE is released. +As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440. Note that there could be some minor visual glitches when running in vertical orientation (this will be fixed in future ES-DE releases) and for the best experience you will probably need to use a customized theme set when running at extreme or unusual resolutions. + The installation procedure is just covered briefly here and may differ a bit for your specific operating system, so in case of problems refer to your system documentation. **Installing a Linux .deb package** diff --git a/es-app/src/guis/GuiAlternativeEmulators.cpp b/es-app/src/guis/GuiAlternativeEmulators.cpp index 50420a17b..e19de6ef9 100644 --- a/es-app/src/guis/GuiAlternativeEmulators.cpp +++ b/es-app/src/guis/GuiAlternativeEmulators.cpp @@ -39,13 +39,6 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) ComponentListRow row; - // This transparent bracket is only added to generate a left margin. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - std::string name = (*it)->getName(); std::shared_ptr systemText = std::make_shared(mWindow, name, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); @@ -94,7 +87,9 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) labelText->setColor(TEXTCOLOR_SCRAPERMARKED); mCommandRows[name] = labelText; - labelText->setSize(labelSizeX, labelText->getSize().y); + labelText->setSize(mMenu.getSize().x - systemSizeX - + 20.0f * Renderer::getScreenHeightModifier(), + systemText->getSize().y); row.addElement(labelText, false); row.makeAcceptInputHandler([this, it, labelText] { @@ -157,6 +152,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_LEFT); + labelText->setSelectable(true); if (system->getSystemEnvData()->mLaunchCommands.front().second == label) labelText->setValue(labelText->getValue().append(" [DEFAULT]")); @@ -193,13 +189,6 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) delete s; }); - // This transparent bracket is only added to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - // Select the row that corresponds to the selected label. if (selectedLabel == label) s->addRow(row, true); diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 2d1e31059..fd18b1cb5 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -177,12 +177,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st row.makeAcceptInputHandler(createCollectionCall); auto themeFolder = std::make_shared( mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); + themeFolder->setSelectable(true); row.addElement(themeFolder, true); - // This transparent bracket is only added to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - row.addElement(bracket, false); ss->addRow(row); } mWindow->pushGui(ss); @@ -287,15 +283,17 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st }; row.makeAcceptInputHandler(deleteCollectionCall); auto customCollection = std::make_shared( - mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); + mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + customCollection->setSelectable(true); row.addElement(customCollection, true); - // This transparent bracket is only added generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - row.addElement(bracket, false); ss->addRow(row); } + // Make the menu slightly wider to fit the scroll indicators. + glm::vec2 menuSize{ss->getMenuSize()}; + glm::vec3 menuPos{ss->getMenuPosition()}; + ss->setMenuSize(glm::vec2{menuSize.x * 1.08f, menuSize.y}); + menuPos.x = static_cast((Renderer::getScreenWidth()) - ss->getMenuSize().x) / 2.0f; + ss->setMenuPosition(menuPos); mWindow->pushGui(ss); }); addRow(row); diff --git a/es-app/src/guis/GuiGameScraper.cpp b/es-app/src/guis/GuiGameScraper.cpp index 9ddc85caa..bcda1e93f 100644 --- a/es-app/src/guis/GuiGameScraper.cpp +++ b/es-app/src/guis/GuiGameScraper.cpp @@ -23,15 +23,13 @@ GuiGameScraper::GuiGameScraper(Window* window, std::function doneFunc) : GuiComponent(window) , mClose(false) - , mGrid(window, glm::ivec2{1, 7}) + , mGrid(window, glm::ivec2{2, 6}) , mBox(window, ":/graphics/frame.svg") , mSearchParams(params) { addChild(&mBox); addChild(&mGrid); - // Row 0 is a spacer. - std::string scrapeName; if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { @@ -51,21 +49,37 @@ GuiGameScraper::GuiGameScraper(Window* window, mWindow, scrapeName + ((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""), - Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); - mGrid.setEntry(mGameName, glm::ivec2{0, 1}, false, true); - - // Row 2 is a spacer. + Font::get(FONT_SIZE_LARGE), 0x777777FF, ALIGN_CENTER); + mGameName->setColor(0x555555FF); + mGrid.setEntry(mGameName, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); mSystemName = std::make_shared( mWindow, Utils::String::toUpper(mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); - mGrid.setEntry(mSystemName, glm::ivec2{0, 3}, false, true); + mGrid.setEntry(mSystemName, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); - // Row 4 is a spacer. + // Row 3 is a spacer. // GuiScraperSearch. mSearch = std::make_shared(window, GuiScraperSearch::NEVER_AUTO_ACCEPT, 1); - mGrid.setEntry(mSearch, glm::ivec2{0, 5}, true); + mGrid.setEntry(mSearch, glm::ivec2{0, 4}, true, true, glm::ivec2{2, 1}); + + mResultList = mSearch->getResultList(); + + // Set up scroll indicators. + mScrollUp = std::make_shared(mWindow); + mScrollDown = std::make_shared(mWindow); + mScrollIndicator = + std::make_shared(mResultList, mScrollUp, mScrollDown); + + mScrollUp->setResize(0.0f, mGameName->getFont()->getLetterHeight() / 2.0f); + mScrollUp->setOrigin(0.0f, -0.35f); + + mScrollDown->setResize(0.0f, mGameName->getFont()->getLetterHeight() / 2.0f); + mScrollDown->setOrigin(0.0f, 0.35f); + + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); // Buttons std::vector> buttons; @@ -74,6 +88,9 @@ GuiGameScraper::GuiGameScraper(Window* window, std::make_shared(mWindow, "REFINE SEARCH", "refine search", [&] { // Refine the search, unless the result has already been accepted. if (!mSearch->getAcceptedResult()) { + // Copy any search refine that may have been previously entered by opening + // the input screen using the "Y" button shortcut. + mSearchParams.nameOverride = mSearch->getNameOverride(); mSearch->openInputScreen(mSearchParams); mGrid.resetCursor(); } @@ -92,20 +109,34 @@ GuiGameScraper::GuiGameScraper(Window* window, })); mButtonGrid = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) { doneFunc(result); close(); }); mSearch->setCancelCallback([&] { delete this; }); + mSearch->setRefineCallback([&] { + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + }); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is // the 16:9 reference. float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); - setSize(width, Renderer::getScreenHeight() * 0.747f); + float height = (mGameName->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.0637f) + + mSystemName->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.04f + + mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f; + + // TODO: Temporary hack, see below. + height -= 7.0f * Renderer::getScreenHeightModifier(); + + setSize(width, height); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); @@ -115,15 +146,31 @@ GuiGameScraper::GuiGameScraper(Window* window, void GuiGameScraper::onSizeChanged() { + mGrid.setRowHeightPerc( + 0, (mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc( + 1, (mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc(2, mSystemName->getFont()->getLetterHeight() / mSize.y, false); + mGrid.setRowHeightPerc(3, 0.04f, false); + mGrid.setRowHeightPerc(4, ((Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f)) / mSize.y, false); + + // TODO: Replace this temporary hack with a proper solution. There is some kind of rounding + // issue somewhere that causes a small alignment error. This code partly compensates for this + // at higher resolutions than 1920x1080. + if (Renderer::getScreenHeightModifier() > 1.0f) + mSize.y -= 3.0f * Renderer::getScreenHeightModifier(); + + mGrid.setColWidthPerc(1, 0.04f); + + mGrid.setSize(mSize); mBox.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); - mGrid.setRowHeightPerc(0, 0.04f, false); - mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() / mSize.y, false); - mGrid.setRowHeightPerc(2, 0.04f, false); - mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() / mSize.y, false); - mGrid.setRowHeightPerc(4, 0.04f, false); - mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y / mSize.y, false); - mGrid.setSize(mSize); + // Add some extra margins to the game name. + const float newSizeX = mSize.x * 0.96f; + mGameName->setSize(newSizeX, mGameName->getSize().y); + mGameName->setPosition((mSize.x - newSizeX) / 2.0f, 0.0f); } bool GuiGameScraper::input(InputConfig* config, Input input) diff --git a/es-app/src/guis/GuiGameScraper.h b/es-app/src/guis/GuiGameScraper.h index b910c1351..040f1e072 100644 --- a/es-app/src/guis/GuiGameScraper.h +++ b/es-app/src/guis/GuiGameScraper.h @@ -13,6 +13,7 @@ #include "GuiComponent.h" #include "components/NinePatchComponent.h" +#include "components/ScrollIndicatorComponent.h" #include "guis/GuiScraperSearch.h" class GuiGameScraper : public GuiComponent @@ -38,9 +39,13 @@ private: NinePatchComponent mBox; std::shared_ptr mGameName; + std::shared_ptr mScrollUp; + std::shared_ptr mScrollDown; + std::shared_ptr mScrollIndicator; std::shared_ptr mSystemName; std::shared_ptr mSearch; std::shared_ptr mButtonGrid; + std::shared_ptr mResultList; ScraperSearchParams mSearchParams; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 74f707d68..ffa9cd0f2 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -61,7 +61,7 @@ GuiMenu::GuiMenu(Window* window) if (isFullUI) addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherOptions(); }); - // TEMPORARY - disabled for now, will be used in the future. + // TEMPORARY: Disabled for now, will be used in the future. // if (isFullUI) // addEntry("UTILITIES", 0x777777FF, true, [this] { // openUtilitiesMenu(); }); @@ -600,8 +600,8 @@ void GuiMenu::openSoundOptions() { auto s = new GuiSettings(mWindow, "SOUND SETTINGS"); -// TEMPORARY - Hide the volume slider on macOS and BSD Unix until the volume control logic -// has been implemented for these operating systems. +// TODO: Hide the volume slider on macOS and BSD Unix until the volume control logic has been +// implemented for these operating systems. #if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) // System volume. auto system_volume = std::make_shared(mWindow, 0.f, 100.f, 1.f, "%"); @@ -1207,11 +1207,6 @@ void GuiMenu::openQuitMenu() Window* window = mWindow; HelpStyle style = getHelpStyle(); - // This transparent bracket is only neeeded to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - ComponentListRow row; row.makeAcceptInputHandler([window, this] { @@ -1224,10 +1219,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "QUIT EMULATIONSTATION", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto quitText = std::make_shared(window, "QUIT EMULATIONSTATION", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + quitText->setSelectable(true); + row.addElement(quitText, true); s->addRow(row); row.elements.clear(); @@ -1243,10 +1238,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "REBOOT SYSTEM", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto rebootText = std::make_shared(window, "REBOOT SYSTEM", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + rebootText->setSelectable(true); + row.addElement(rebootText, true); s->addRow(row); row.elements.clear(); @@ -1262,10 +1257,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "POWER OFF SYSTEM", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto powerOffText = std::make_shared( + window, "POWER OFF SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + powerOffText->setSelectable(true); + row.addElement(powerOffText, true); s->addRow(row); mWindow->pushGui(s); diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index cf12889a7..defc04f04 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -43,7 +43,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::function deleteGameFunc) : GuiComponent{window} , mBackground{window, ":/graphics/frame.svg"} - , mGrid{window, glm::ivec2{3, 6}} + , mGrid{window, glm::ivec2{2, 6}} , mScraperParams{scraperParams} , mMetaDataDecl{mdd} , mMetaData{md} @@ -58,7 +58,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, mTitle = std::make_shared(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{3, 2}); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); // Extract possible subfolders from the path. std::string folderPath = @@ -80,13 +80,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, folderPath + Utils::FileSystem::getFileName(scraperParams.game->getPath()) + " [" + Utils::String::toUpper(scraperParams.system->getName()) + "]" + (scraperParams.game->getType() == FOLDER ? " " + ViewController::FOLDER_CHAR : ""), - Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER, glm::vec3{}, glm::vec2{}, 0x00000000, - 0.05f); + Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER); - mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{3, 1}); + mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); mList = std::make_shared(mWindow); - mGrid.setEntry(mList, glm::ivec2{0, 4}, true, true, glm::ivec2{3, 1}); + mGrid.setEntry(mList, glm::ivec2{0, 4}, true, true, glm::ivec2{2, 1}); // Set up scroll indicators. mScrollUp = std::make_shared(mWindow); @@ -99,8 +98,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); mScrollDown->setOrigin(0.0f, 0.35f); - mGrid.setEntry(mScrollUp, glm::ivec2{2, 0}, false, false, glm::ivec2{1, 1}); - mGrid.setEntry(mScrollDown, glm::ivec2{2, 1}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); // Populate list. for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) { @@ -265,6 +264,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + labelText->setSelectable(true); if (scraperParams.system->getAlternativeEmulator() == "" && scraperParams.system->getSystemEnvData() @@ -289,14 +289,6 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, delete s; }); - // This transparent bracket is only added to generate the correct help - // prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - // Select the row that corresponds to the selected label. if (selectedLabel == label) s->addRow(row, true); @@ -314,13 +306,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, static_cast(Renderer::getScreenWidth()) * maxWidthModifier; s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y}); - - auto menuSize = s->getMenuSize(); - auto menuPos = s->getMenuPosition(); - - s->setMenuPosition(glm::vec3{(s->getSize().x - menuSize.x) / 2.0f, - (s->getSize().y - menuSize.y) / 3.0f, - menuPos.z}); + s->setMenuPosition(glm::vec3{(s->getSize().x - maxWidth) / 2.0f, + mPosition.y, mPosition.z}); mWindow->pushGui(s); }); } @@ -481,7 +468,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, } mButtons = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtons, glm::ivec2{0, 5}, true, false, glm::ivec2{3, 1}); + mGrid.setEntry(mButtons, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); // Resize + center. float width = @@ -505,14 +492,18 @@ void GuiMetaDataEd::onSizeChanged() mGrid.setRowHeightPerc(3, (titleSubtitleSpacing * 1.2f) / mSize.y); mGrid.setRowHeightPerc(4, ((mList->getRowHeight(0) * 10.0f) + 2.0f) / mSize.y); - mGrid.setColWidthPerc(0, 0.07f); - mGrid.setColWidthPerc(2, 0.07f); + mGrid.setColWidthPerc(1, 0.055f); mGrid.setSize(mSize); mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); + + // Add some extra margins to the file/folder name. + const float newSizeX = mSize.x * 0.96f; + mSubtitle->setSize(newSizeX, mSubtitle->getSize().y); + mSubtitle->setPosition((mSize.x - newSizeX) / 2.0f, mSubtitle->getPosition().y); } void GuiMetaDataEd::save() diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 067352199..50b8aab67 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -28,7 +28,7 @@ GuiScraperMulti::GuiScraperMulti(Window* window, bool approveResults) : GuiComponent(window) , mBackground(window, ":/graphics/frame.svg") - , mGrid(window, glm::ivec2{1, 5}) + , mGrid(window, glm::ivec2{2, 6}) , mSearchQueue(searches) , mApproveResults(approveResults) { @@ -47,15 +47,15 @@ GuiScraperMulti::GuiScraperMulti(Window* window, // Set up grid. mTitle = std::make_shared(mWindow, "SCRAPING IN PROGRESS", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); mSystem = std::make_shared(mWindow, "SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); - mGrid.setEntry(mSystem, glm::ivec2{0, 1}, false, true); + mGrid.setEntry(mSystem, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); mSubtitle = std::make_shared( mWindow, "subtitle text", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); - mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true); + mGrid.setEntry(mSubtitle, glm::ivec2{0, 3}, false, true, glm::ivec2{2, 1}); if (mApproveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic")) mSearchComp = std::make_shared( @@ -70,10 +70,34 @@ GuiScraperMulti::GuiScraperMulti(Window* window, std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1)); mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this)); mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this)); - mGrid.setEntry(mSearchComp, glm::ivec2{0, 3}, - mSearchComp->getSearchType() != GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, - true); + mSearchComp->setRefineCallback([&] { + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + }); + mGrid.setEntry(mSearchComp, glm::ivec2{0, 4}, + mSearchComp->getSearchType() != GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, + true, glm::ivec2{2, 1}); + + mResultList = mSearchComp->getResultList(); + + // Set up scroll indicators. + mScrollUp = std::make_shared(mWindow); + mScrollDown = std::make_shared(mWindow); + mScrollIndicator = + std::make_shared(mResultList, mScrollUp, mScrollDown); + + mScrollUp->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); + mScrollUp->setOrigin(0.0f, -0.35f); + + mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); + mScrollDown->setOrigin(0.0f, 0.35f); + + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); + + // Buttons. std::vector> buttons; if (mApproveResults) { @@ -125,14 +149,23 @@ GuiScraperMulti::GuiScraperMulti(Window* window, std::bind(&GuiScraperMulti::finish, this))); mButtonGrid = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 4}, true, false); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is // the 16:9 reference. float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); - setSize(width, Renderer::getScreenHeight() * 0.849f); + float height = (mTitle->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.0637f) + + mSystem->getFont()->getLetterHeight() + + mSubtitle->getFont()->getHeight() * 1.75f + mButtonGrid->getSize().y + + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f; + + // TODO: Temporary hack, see below. + height -= 7.0f * Renderer::getScreenHeightModifier(); + + setSize(width, height); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); @@ -153,13 +186,26 @@ GuiScraperMulti::~GuiScraperMulti() void GuiScraperMulti::onSizeChanged() { - mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); + mGrid.setRowHeightPerc( + 0, (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc( + 1, (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc(2, (mSystem->getFont()->getLetterHeight()) / mSize.y, false); + mGrid.setRowHeightPerc(3, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y, false); + mGrid.setRowHeightPerc(4, ((Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f)) / mSize.y, false); + + // TODO: Replace this temporary hack with a proper solution. There is some kind of rounding + // issue somewhere that causes a small alignment error. This code partly compensates for this + // at higher resolutions than 1920x1080. + if (Renderer::getScreenHeightModifier() > 1.0f) + mSize.y -= 3.0f * Renderer::getScreenHeightModifier(); + + mGrid.setColWidthPerc(1, 0.04f); - mGrid.setRowHeightPerc(0, mTitle->getFont()->getLetterHeight() * 1.9725f / mSize.y, false); - mGrid.setRowHeightPerc(1, (mSystem->getFont()->getLetterHeight() + 2.0f) / mSize.y, false); - mGrid.setRowHeightPerc(2, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y, false); - mGrid.setRowHeightPerc(4, mButtonGrid->getSize().y / mSize.y, false); mGrid.setSize(mSize); + mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); } void GuiScraperMulti::doNextSearch() @@ -189,6 +235,10 @@ void GuiScraperMulti::doNextSearch() scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()); } + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + // Extract possible subfolders from the path. std::string folderPath = Utils::String::replace(Utils::FileSystem::getParent(mSearchQueue.front().game->getPath()), @@ -264,9 +314,6 @@ void GuiScraperMulti::finish() std::vector GuiScraperMulti::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - // Remove the 'Choose' entry if in fully automatic mode. - if (!mApproveResults) - prompts.pop_back(); return prompts; } diff --git a/es-app/src/guis/GuiScraperMulti.h b/es-app/src/guis/GuiScraperMulti.h index 63c755f6b..d71525086 100644 --- a/es-app/src/guis/GuiScraperMulti.h +++ b/es-app/src/guis/GuiScraperMulti.h @@ -16,6 +16,7 @@ #include "MetaData.h" #include "components/ComponentGrid.h" #include "components/NinePatchComponent.h" +#include "components/ScrollIndicatorComponent.h" #include "scrapers/Scraper.h" class GuiScraperSearch; @@ -45,10 +46,14 @@ private: ComponentGrid mGrid; std::shared_ptr mTitle; + std::shared_ptr mScrollUp; + std::shared_ptr mScrollDown; + std::shared_ptr mScrollIndicator; std::shared_ptr mSystem; std::shared_ptr mSubtitle; std::shared_ptr mSearchComp; std::shared_ptr mButtonGrid; + std::shared_ptr mResultList; std::queue mSearchQueue; std::vector mMetaDataDecl; diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 340eb81e2..bf7aba7b9 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -39,7 +39,7 @@ GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount) : GuiComponent(window) - , mGrid(window, glm::ivec2{4, 3}) + , mGrid(window, glm::ivec2{5, 3}) , mSearchType(type) , mScrapeCount(scrapeCount) , mRefinedSearch(false) @@ -88,14 +88,11 @@ GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int mMD_ReleaseDate = std::make_shared(mWindow); mMD_ReleaseDate->setColor(mdColor); mMD_ReleaseDate->setUppercase(true); - mMD_Developer = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); - mMD_Publisher = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); - mMD_Genre = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, glm::vec3{}, - glm::vec2{}, 0x00000000, 0.02f); - mMD_Players = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); + mMD_Developer = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); + mMD_Publisher = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); + mMD_Genre = + std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, glm::vec3{}); + mMD_Players = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); mMD_Filler = std::make_shared(mWindow, "", font, mdColor); if (Settings::getInstance()->getString("Scraper") != "thegamesdb") @@ -193,45 +190,47 @@ void GuiScraperSearch::onSizeChanged() mGrid.setColWidthPerc(1, 0.25f); if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) - mGrid.setColWidthPerc(2, 0.25f); + mGrid.setColWidthPerc(2, 0.33f); else - mGrid.setColWidthPerc(2, 0.28f); + mGrid.setColWidthPerc(2, 0.30f); // Row heights. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // Show name. mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) / mGrid.getSize().y); // Result name. else - mGrid.setRowHeightPerc(0, 0.0825f); // Hide name but do padding. + mGrid.setRowHeightPerc(0, 0.0725f); // Hide name but do padding. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) mGrid.setRowHeightPerc(2, 0.2f); else mGrid.setRowHeightPerc(1, 0.505f); - const float boxartCellScale = 0.9f; + const float thumbnailCellScale = 0.93f; // Limit thumbnail size using setMaxHeight - we do this instead of letting mGrid // call setSize because it maintains the aspect ratio. // We also pad a little so it doesn't rub up against the metadata labels. - mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1)); + mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * thumbnailCellScale, mGrid.getRowHeight(1)); // Metadata. resizeMetadata(); + // Small vertical spacer between the metadata fields and the result list. + mGrid.setColWidthPerc(3, 0.004f); + if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT) - mDescContainer->setSize(mGrid.getColWidth(1) * boxartCellScale + mGrid.getColWidth(2), + mDescContainer->setSize(mGrid.getColWidth(1) * thumbnailCellScale + mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3.0f); else - mDescContainer->setSize(mGrid.getColWidth(3) * boxartCellScale, - mResultDesc->getFont()->getHeight() * 6.0f); + mDescContainer->setSize(mGrid.getColWidth(4) * thumbnailCellScale, + mResultDesc->getFont()->getHeight() * 8.0f); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0.0f); // Set the width of mResultName to the cell width so that text abbreviation will work correctly. - glm::vec2 resultNameSize{mResultName->getSize()}; - mResultName->setSize(mGrid.getColWidth(3), resultNameSize.y); + mResultName->setSize(mGrid.getColWidth(1) + mGrid.getColWidth(2), mResultName->getSize().y); mGrid.onSizeChanged(); mBusyAnim.setSize(mSize); @@ -289,30 +288,30 @@ void GuiScraperSearch::updateViewStyle() // Add them back depending on search type. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) { // Show name. - mGrid.setEntry(mResultName, glm::ivec2{1, 0}, false, false, glm::ivec2{2, 1}, + mGrid.setEntry(mResultName, glm::ivec2{1, 0}, false, false, glm::ivec2{3, 1}, GridFlags::BORDER_TOP); // Need a border on the bottom left. mGrid.setEntry(std::make_shared(mWindow), glm::ivec2{0, 2}, false, false, - glm::ivec2{3, 1}, GridFlags::BORDER_BOTTOM); + glm::ivec2{4, 1}, GridFlags::BORDER_BOTTOM); // Show description on the right. - mGrid.setEntry(mDescContainer, glm::ivec2{3, 0}, false, false, glm::ivec2{1, 3}, - GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); + mGrid.setEntry(mDescContainer, glm::ivec2{4, 0}, false, false, glm::ivec2{1, 3}, + GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM | GridFlags::BORDER_LEFT); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0.0f); } else { // Fake row where name would be. mGrid.setEntry(std::make_shared(mWindow), glm::ivec2{1, 0}, false, true, - glm::ivec2{2, 1}, GridFlags::BORDER_TOP); + glm::ivec2{3, 1}, GridFlags::BORDER_TOP); // Show result list on the right. - mGrid.setEntry(mResultList, glm::ivec2{3, 0}, true, true, glm::ivec2{1, 3}, + mGrid.setEntry(mResultList, glm::ivec2{4, 0}, true, true, glm::ivec2{1, 3}, GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); // Show description under image/info. - mGrid.setEntry(mDescContainer, glm::ivec2{1, 2}, false, false, glm::ivec2{2, 1}, + mGrid.setEntry(mDescContainer, glm::ivec2{1, 2}, false, false, glm::ivec2{3, 1}, GridFlags::BORDER_BOTTOM); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0); @@ -328,6 +327,7 @@ void GuiScraperSearch::search(const ScraperSearchParams& params) mScrapeResult = {}; mResultList->clear(); + mResultList->setLoopRows(false); mScraperResults.clear(); mMDRetrieveURLsHandle.reset(); mThumbnailReqMap.clear(); @@ -356,6 +356,7 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu mResultList->clear(); mScraperResults = results; + mResultList->setLoopRows(true); auto font = Font::get(FONT_SIZE_MEDIUM); unsigned int color = 0x777777FF; @@ -390,7 +391,7 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu row.addElement( std::make_shared( mWindow, Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), - true); + false); row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); mResultList->addRow(row); } @@ -563,8 +564,10 @@ bool GuiScraperSearch::input(InputConfig* config, Input input) else if (mSearchType == ACCEPT_SINGLE_MATCHES && !mFoundGame) allowRefine = true; - if (allowRefine) + if (allowRefine) { + mResultList->stopLooping(); openInputScreen(mLastSearch); + } } // If multi-scraping, skip game unless the result has already been accepted. @@ -590,6 +593,7 @@ void GuiScraperSearch::render(const glm::mat4& parentTrans) void GuiScraperSearch::returnResult(ScraperSearchResult result) { + mResultList->setLoopRows(false); mBlockAccept = true; mAcceptedResult = true; @@ -798,6 +802,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) stop(); mRefinedSearch = true; params.nameOverride = name; + if (mRefineCallback != nullptr) + mRefineCallback(); search(params); }; diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index 449124ba0..082e8e3b0 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -71,6 +71,10 @@ public: { mCancelCallback = cancelCallback; } + void setRefineCallback(const std::function& refineCallback) + { + mRefineCallback = refineCallback; + } bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; @@ -92,6 +96,8 @@ public: void onFocusGained() override { mGrid.onFocusGained(); } void onFocusLost() override { mGrid.onFocusLost(); } + std::shared_ptr& getResultList() { return mResultList; } + private: void updateViewStyle(); void updateThumbnail(); @@ -152,6 +158,7 @@ private: std::function mAcceptCallback; std::function mSkipCallback; std::function mCancelCallback; + std::function mRefineCallback; unsigned int mScrapeCount; bool mRefinedSearch; bool mBlockAccept; diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index be86c130f..c68c06bbb 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -232,7 +232,7 @@ bool parseArgs(int argc, char* argv[]) } int width = atoi(argv[i + 1]); int height = atoi(argv[i + 2]); - if (width < 224 || height < 224 || width > 7680 || height > 4320 || + if (width < 224 || height < 224 || width > 7680 || height > 7680 || height < width / 4 || width < height / 2) { std::cerr << "Error: Unsupported resolution " << width << "x" << height << " supplied.\n"; diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 1efa09469..284762deb 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -493,7 +493,7 @@ void GridGameListView::updateInfoPanel() if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) || (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) { - // TEMPORARY - This does not seem to work, needs to be reviewed later. + // TODO: This does not seem to work, needs to be reviewed later. // auto func = [comp](float t) { auto func = [](float t) { // comp->setOpacity(static_cast(glm::mix(0.0f, 1.0f, t) * 255)); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index a69705ea9..e7035633c 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -181,7 +181,7 @@ void Settings::setDefaults() mBoolMap["SpecialCharsASCII"] = {false, false}; mBoolMap["ListScrollOverlay"] = {false, false}; mBoolMap["VirtualKeyboard"] = {true, true}; - mBoolMap["ScrollIndicators"] = {false, false}; + mBoolMap["ScrollIndicators"] = {true, true}; mBoolMap["FavoritesAddButton"] = {true, true}; mBoolMap["RandomAddButton"] = {false, false}; mBoolMap["GamelistFilters"] = {true, true}; diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index c829399ee..1457e3d14 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -8,6 +8,8 @@ #include "components/ComponentList.h" +#include "resources/Font.h" + #define TOTAL_HORIZONTAL_PADDING_PX 20.0f ComponentList::ComponentList(Window* window) @@ -15,8 +17,14 @@ ComponentList::ComponentList(Window* window) , mFocused{false} , mSetupCompleted{false} , mBottomCameraOffset{false} + , mSingleRowScroll{false} , mSelectorBarOffset{0.0f} , mCameraOffset{0.0f} + , mLoopRows{false} + , mLoopScroll{false} + , mLoopOffset{0} + , mLoopOffset2{0} + , mLoopTime{0} , mScrollIndicatorStatus{SCROLL_NONE} { // Adjust the padding relative to the aspect ratio and screen resolution to make it look @@ -63,6 +71,8 @@ bool ComponentList::input(InputConfig* config, Input input) if (size() == 0) return false; + mSingleRowScroll = false; + if (input.value && (config->isMappedTo("a", input) || config->isMappedLike("lefttrigger", input) || config->isMappedLike("righttrigger", input))) { @@ -86,9 +96,11 @@ bool ComponentList::input(InputConfig* config, Input input) // Input handler didn't consume the input - try to scroll. if (config->isMappedLike("up", input)) { + mSingleRowScroll = true; return listInput(input.value != 0 ? -1 : 0); } else if (config->isMappedLike("down", input)) { + mSingleRowScroll = true; return listInput(input.value != 0 ? 1 : 0); } else if (config->isMappedLike("leftshoulder", input)) { @@ -115,6 +127,12 @@ bool ComponentList::input(InputConfig* config, Input input) void ComponentList::update(int deltaTime) { + if (!mFocused && mLoopRows) { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + const float totalHeight = getTotalRowHeight(); // Scroll indicator logic, used by ScrollIndicatorComponent. @@ -142,15 +160,44 @@ void ComponentList::update(int deltaTime) } if (scrollIndicatorChanged == true && mScrollIndicatorChangedCallback != nullptr) - mScrollIndicatorChangedCallback(mScrollIndicatorStatus); + mScrollIndicatorChangedCallback(mScrollIndicatorStatus, mSingleRowScroll); listUpdate(deltaTime); if (size()) { + float rowWidth{0.0f}; + // Update our currently selected row. for (auto it = mEntries.at(mCursor).data.elements.cbegin(); - it != mEntries.at(mCursor).data.elements.cend(); it++) + it != mEntries.at(mCursor).data.elements.cend(); it++) { it->component->update(deltaTime); + rowWidth += it->component->getSize().x; + } + + if (mLoopRows && rowWidth + mHorizontalPadding / 2.0f > mSize.x) { + // Loop the text. + const float speed{ + Font::get(FONT_SIZE_MEDIUM)->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f}; + const float delay{1500.0f}; + const float scrollLength{rowWidth}; + const float returnLength{speed * 1.5f}; + const float scrollTime{(scrollLength * 1000.0f) / speed}; + const float returnTime{(returnLength * 1000.0f) / speed}; + const int maxTime{static_cast(delay + scrollTime + returnTime)}; + + mLoopTime += deltaTime; + while (mLoopTime > maxTime) + mLoopTime -= maxTime; + + mLoopOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, + static_cast(mLoopTime), + scrollLength + returnLength)); + + if (mLoopOffset > (scrollLength - (mSize.x - returnLength))) + mLoopOffset2 = static_cast(mLoopOffset - (scrollLength + returnLength)); + else if (mLoopOffset2 < 0) + mLoopOffset2 = 0; + } } } @@ -158,6 +205,12 @@ void ComponentList::onCursorChanged(const CursorState& state) { mSetupCompleted = true; + if (mLoopRows) { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + // Update the selector bar position. // In the future this might be animated. mSelectorBarOffset = 0; @@ -203,7 +256,9 @@ void ComponentList::updateCameraOffset() i++; } - if (mCameraOffset < oldCameraOffset) + if (mCameraOffset < oldCameraOffset && + (oldCameraOffset > mSelectorBarOffset || + mScrollIndicatorStatus != ComponentList::SCROLL_NONE)) mBottomCameraOffset = false; if (mCameraOffset < 0.0f) @@ -226,22 +281,47 @@ void ComponentList::render(const glm::mat4& parentTrans) dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x; dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y; - Renderer::pushClipRect( - glm::ivec2{static_cast(std::round(trans[3].x)), - static_cast(std::round(trans[3].y))}, - glm::ivec2{static_cast(std::round(dim.x)), static_cast(std::round(dim.y))}); + const int clipRectPosX{static_cast(std::round(trans[3].x))}; + const int clipRectPosY{static_cast(std::round(trans[3].y))}; + const int clipRectSizeX{static_cast(std::round(dim.x))}; + const int clipRectSizeY{static_cast(std::round(dim.y))}; + + Renderer::pushClipRect(glm::ivec2{clipRectPosX, clipRectPosY}, + glm::ivec2{clipRectSizeX, clipRectSizeY}); // Scroll the camera. trans = glm::translate(trans, glm::vec3{0.0f, -mCameraOffset, 0.0f}); + glm::mat4 loopTrans{trans}; + // Draw our entries. std::vector drawAfterCursor; bool drawAll; for (size_t i = 0; i < mEntries.size(); i++) { + + if (mLoopRows && mFocused && mLoopOffset > 0) { + loopTrans = + glm::translate(trans, glm::vec3{static_cast(-mLoopOffset), 0.0f, 0.0f}); + } + auto& entry = mEntries.at(i); drawAll = !mFocused || i != static_cast(mCursor); for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) { if (drawAll || it->invert_when_selected) { + auto renderLoopFunc = [&]() { + // Needed to avoid flickering when returning to the start position. + if (mLoopOffset == 0 && mLoopOffset2 == 0) + mLoopScroll = false; + it->component->render(loopTrans); + // Render row again if text is moved far enough for it to repeat. + if (mLoopOffset2 < 0 || mLoopScroll) { + mLoopScroll = true; + loopTrans = glm::translate( + trans, glm::vec3{static_cast(-mLoopOffset2), 0.0f, 0.0f}); + it->component->render(loopTrans); + } + }; + // For the row where the cursor is at, we want to remove any hue from the // font or image before inverting, as it would otherwise lead to an ugly // inverted color (e.g. red inverting to a green hue). @@ -260,15 +340,14 @@ void ComponentList::render(const glm::mat4& parentTrans) unsigned char byteBlue = origColor >> 8 & 0xFF; // If it's neutral, just proceed with normal rendering. if (byteRed == byteGreen && byteGreen == byteBlue) { - it->component->render(trans); + renderLoopFunc(); } else { if (isTextComponent) it->component->setColor(DEFAULT_INVERTED_TEXTCOLOR); else it->component->setColorShift(DEFAULT_INVERTED_IMAGECOLOR); - - it->component->render(trans); + renderLoopFunc(); // Revert to the original color after rendering. if (isTextComponent) it->component->setColor(origColor); diff --git a/es-core/src/components/ComponentList.h b/es-core/src/components/ComponentList.h index 9035cd881..8b9d9f76c 100644 --- a/es-core/src/components/ComponentList.h +++ b/es-core/src/components/ComponentList.h @@ -86,6 +86,22 @@ public: float getTotalRowHeight() const; float getRowHeight(int row) const { return getRowHeight(mEntries.at(row).data); } + // Horizontal looping for row content that doesn't fit on-screen. + void setLoopRows(bool state) { mLoopRows = state; } + void stopLooping() + { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + + void resetScrollIndicatorStatus() + { + mScrollIndicatorStatus = SCROLL_NONE; + if (mScrollIndicatorChangedCallback != nullptr) + mScrollIndicatorChangedCallback(mScrollIndicatorStatus, false); + } + void setCursorChangedCallback(const std::function& callback) { mCursorChangedCallback = callback; @@ -95,7 +111,7 @@ public: return mCursorChangedCallback; } void setScrollIndicatorChangedCallback( - const std::function& callback) + const std::function& callback) { mScrollIndicatorChangedCallback = callback; } @@ -107,6 +123,7 @@ private: bool mFocused; bool mSetupCompleted; bool mBottomCameraOffset; + bool mSingleRowScroll; void updateCameraOffset(); void updateElementPosition(const ComponentListRow& row); @@ -118,8 +135,15 @@ private: float mSelectorBarOffset; float mCameraOffset; + bool mLoopRows; + bool mLoopScroll; + int mLoopOffset; + int mLoopOffset2; + int mLoopTime; + std::function mCursorChangedCallback; - std::function mScrollIndicatorChangedCallback; + std::function + mScrollIndicatorChangedCallback; ScrollIndicator mScrollIndicatorStatus; }; diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index f9b81abd9..4979de675 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -122,7 +122,7 @@ void FlexboxComponent::computeLayout() newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; if (image.second.getSize() != newSize) - image.second.setResize(newSize.x, newSize.y); + image.second.setResize(std::round(newSize.x), std::round(newSize.y)); // In case maxItemSize needs to be updated. if (newSize.x != sizeChange.x) @@ -132,9 +132,9 @@ void FlexboxComponent::computeLayout() } if (maxItemSize.x != sizeChange.x) - maxItemSize.x = sizeChange.x; + maxItemSize.x = std::round(sizeChange.x); if (maxItemSize.y != sizeChange.y) - maxItemSize.y = sizeChange.y; + maxItemSize.y = std::round(sizeChange.y); // Pre-compute layout parameters. float anchorXStart{anchorX}; @@ -166,10 +166,9 @@ void FlexboxComponent::computeLayout() image.second.setSize(image.second.getSize().x, maxItemSize.y); } - // TODO: Doesn't work correctly. // Apply overall container alignment. if (mAlignment == "right") - x += (mSize.x - size.x * grid.x) - mItemMargin.x; + x += (mSize.x - maxItemSize.x * grid.x - (grid.x - 1) * mItemMargin.x); // Store final item position. image.second.setPosition(x, y); @@ -193,5 +192,21 @@ void FlexboxComponent::computeLayout() } } + // Apply right-align to the images on the last row, if needed. + if (mAlignment == "right") { + std::vector imagesToAlign; + for (auto& image : mImages) { + if (!image.second.isVisible()) + continue; + // Only include images on the last row. + if (image.second.getPosition().y == anchorY) + imagesToAlign.push_back(&image.second); + } + for (auto& moveImage : imagesToAlign) { + float offset = (maxItemSize.x + mItemMargin.x) * (grid.x - imagesToAlign.size()); + moveImage->setPosition(moveImage->getPosition().x + offset, moveImage->getPosition().y); + } + } + mLayoutValid = true; } diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index f6c2d2af2..6da5f3d1a 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -63,11 +63,8 @@ void ImageComponent::resize() else { // SVG rasterization is determined by height and rasterization is done in terms of pixels. // If rounding is off enough in the rasterization step (for images with extreme aspect - // ratios), it can cause cutoff when the aspect ratio breaks. - // So we always make sure the resultant height is an integer to make sure cutoff doesn't - // happen, and scale width from that (you'll see this scattered throughout the function). - // It's important to use floorf rather than round for this, as we never want to round up - // since that can lead to the cutoff just described. + // ratios), it can cause cutoff when the aspect ratio breaks. So we always make sure to + // round accordingly to avoid such issues. if (mTargetIsMax) { mSize = textureSize; @@ -77,13 +74,11 @@ void ImageComponent::resize() // This will be mTargetSize.x. We can't exceed it, nor be lower than it. mSize.x *= resizeScale.x; // We need to make sure we're not creating an image larger than max size. - mSize.y = std::min(floorf(mSize.y * resizeScale.x), mTargetSize.y); + mSize.y = floorf(std::min(mSize.y * resizeScale.x, mTargetSize.y)); } else { // This will be mTargetSize.y(). We can't exceed it. - mSize.y = floorf(mSize.y * resizeScale.y); - // For SVG rasterization, always calculate width from rounded height (see comment - // above). We need to make sure we're not creating an image larger than max size. + mSize.y *= resizeScale.y; mSize.x = std::min((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x); } } @@ -106,9 +101,7 @@ void ImageComponent::resize() float cropPercent = (mSize.x - mTargetSize.x) / (mSize.x * 2.0f); crop(cropPercent, 0.0f, cropPercent, 0.0f); } - // For SVG rasterization, always calculate width from rounded height (see comment - // above). We need to make sure we're not creating an image smaller than min size. - mSize.y = std::max(floorf(mSize.y), mTargetSize.y); + mSize.y = std::max(mSize.y, mTargetSize.y); mSize.x = std::max((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x); } else { @@ -117,23 +110,24 @@ void ImageComponent::resize() mSize = mTargetSize == glm::vec2{} ? textureSize : mTargetSize; // If only one component is set, we resize in a way that maintains aspect ratio. - // For SVG rasterization, we always calculate width from rounded height (see - // comment above). if (!mTargetSize.x && mTargetSize.y) { - mSize.y = floorf(mTargetSize.y); + mSize.y = mTargetSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } else if (mTargetSize.x && !mTargetSize.y) { - mSize.y = floorf((mTargetSize.x / textureSize.x) * textureSize.y); + mSize.y = (mTargetSize.x / textureSize.x) * textureSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } } } - mSize.x = floorf(mSize.x); - mSize.y = floorf(mSize.y); - // mSize.y() should already be rounded. - mTexture->rasterizeAt(static_cast(mSize.x), static_cast(mSize.y)); + // Make sure sub-pixel values are not rounded to zero. + if (mSize.x < 1.0f) + mSize.x = ceilf(mSize.x); + if (mSize.y < 1.0f) + mSize.y = ceilf(mSize.y); + + mTexture->rasterizeAt(mSize.x, mSize.y); onSizeChanged(); } diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index 4f8a2dbf9..f1cfe029d 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -21,7 +21,7 @@ MenuComponent::MenuComponent(Window* window, const std::shared_ptr& titleFont) : GuiComponent(window) , mBackground(window) - , mGrid(window, glm::ivec2{3, 4}) + , mGrid(window, glm::ivec2{2, 4}) , mNeedsSaving(false) { addChild(&mBackground); @@ -34,11 +34,11 @@ MenuComponent::MenuComponent(Window* window, mTitle->setHorizontalAlignment(ALIGN_CENTER); mTitle->setColor(0x555555FF); setTitle(title, titleFont); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{3, 2}); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); // Set up list which will never change (externally, anyway). mList = std::make_shared(mWindow); - mGrid.setEntry(mList, glm::ivec2{0, 2}, true, true, glm::ivec2{3, 1}); + mGrid.setEntry(mList, glm::ivec2{0, 2}, true, true, glm::ivec2{2, 1}); // Set up scroll indicators. mScrollUp = std::make_shared(mWindow); @@ -51,8 +51,8 @@ MenuComponent::MenuComponent(Window* window, mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); mScrollDown->setOrigin(0.0f, 0.35f); - mGrid.setEntry(mScrollUp, glm::ivec2{2, 0}, false, false, glm::ivec2{1, 1}); - mGrid.setEntry(mScrollDown, glm::ivec2{2, 1}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); updateGrid(); updateSize(); @@ -103,7 +103,7 @@ void MenuComponent::updateSize() int i = 0; while (i < mList->size()) { // Add the separator height to the row height so that it also gets properly rendered. - float rowHeight = mList->getRowHeight(i) + (1 * Renderer::getScreenHeightModifier()); + float rowHeight = mList->getRowHeight(i) + (1.0f * Renderer::getScreenHeightModifier()); if (height + rowHeight < maxHeight) height += rowHeight; else @@ -127,8 +127,7 @@ void MenuComponent::onSizeChanged() mGrid.setRowHeightPerc(1, TITLE_HEIGHT / mSize.y / 2.0f); mGrid.setRowHeightPerc(3, getButtonGridHeight() / mSize.y); - mGrid.setColWidthPerc(0, 0.07f); - mGrid.setColWidthPerc(2, 0.07f); + mGrid.setColWidthPerc(1, 0.055f); mGrid.setSize(mSize); } @@ -152,7 +151,7 @@ void MenuComponent::updateGrid() if (mButtons.size()) { mButtonGrid = makeButtonGrid(mWindow, mButtons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 3}, true, false, glm::ivec2{3, 1}); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 3}, true, false, glm::ivec2{2, 1}); } } diff --git a/es-core/src/components/RatingComponent.cpp b/es-core/src/components/RatingComponent.cpp index 6c2c22c6f..7dcdecf7b 100644 --- a/es-core/src/components/RatingComponent.cpp +++ b/es-core/src/components/RatingComponent.cpp @@ -102,11 +102,10 @@ void RatingComponent::onSizeChanged() mSize.x = mSize.y * NUM_RATING_STARS; if (mSize.y > 0.0f) { - size_t heightPx = static_cast(std::round(mSize.y)); if (mFilledTexture) - mFilledTexture->rasterizeAt(heightPx, heightPx); + mFilledTexture->rasterizeAt(mSize.y, mSize.y); if (mUnfilledTexture) - mUnfilledTexture->rasterizeAt(heightPx, heightPx); + mUnfilledTexture->rasterizeAt(mSize.y, mSize.y); } updateVertices(); diff --git a/es-core/src/components/ScrollIndicatorComponent.h b/es-core/src/components/ScrollIndicatorComponent.h index 4c0f5801c..58afa5b9b 100644 --- a/es-core/src/components/ScrollIndicatorComponent.h +++ b/es-core/src/components/ScrollIndicatorComponent.h @@ -34,7 +34,7 @@ public: // If the scroll indicators setting is disabled, then show a permanent down arrow // symbol when the component list contains more entries than can fit on screen. componentList.get()->setScrollIndicatorChangedCallback( - [scrollUp, scrollDown](ComponentList::ScrollIndicator state) { + [scrollUp, scrollDown](ComponentList::ScrollIndicator state, bool singleRowScroll) { if (state == ComponentList::SCROLL_UP || state == ComponentList::SCROLL_UP_DOWN || state == ComponentList::SCROLL_DOWN) { @@ -46,8 +46,9 @@ public: // If the scroll indicator setting is enabled, then also show the up and up/down // combination and switch between these as the list is scrolled. componentList.get()->setScrollIndicatorChangedCallback( - [this, scrollUp, scrollDown](ComponentList::ScrollIndicator state) { - float fadeInTime{FADE_IN_TIME}; + [this, scrollUp, scrollDown](ComponentList::ScrollIndicator state, + bool singleRowScroll) { + float fadeTime{FADE_IN_TIME}; bool upFadeIn = false; bool upFadeOut = false; @@ -68,7 +69,7 @@ public: else if (state == ComponentList::SCROLL_UP && mPreviousScrollState == ComponentList::SCROLL_DOWN) { upFadeIn = true; - fadeInTime *= 1.5f; + fadeTime *= 2.0f; scrollDown->setOpacity(0); } else if (state == ComponentList::SCROLL_UP_DOWN && @@ -95,17 +96,22 @@ public: else if (state == ComponentList::SCROLL_DOWN && mPreviousScrollState == ComponentList::SCROLL_UP) { downFadeIn = true; - fadeInTime *= 1.5f; + fadeTime *= 2.0f; scrollUp->setOpacity(0); } + // If jumping more than one row using the shoulder or trigger buttons, then + // don't fade the indicators. + if (!singleRowScroll) + fadeTime = 0.0f; + if (upFadeIn) { auto upFadeInFunc = [scrollUp](float t) { scrollUp->setOpacity( static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollUp->setAnimation( - new LambdaAnimation(upFadeInFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(upFadeInFunc, static_cast(fadeTime)), 0, nullptr, false); } @@ -115,7 +121,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollUp->setAnimation( - new LambdaAnimation(upFadeOutFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(upFadeOutFunc, static_cast(fadeTime)), 0, nullptr, true); } @@ -125,7 +131,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollDown->setAnimation( - new LambdaAnimation(downFadeInFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(downFadeInFunc, static_cast(fadeTime)), 0, nullptr, false); } @@ -135,7 +141,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollDown->setAnimation( - new LambdaAnimation(downFadeOutFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(downFadeOutFunc, static_cast(fadeTime)), 0, nullptr, true); } diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index 218b7cd3a..5568d0f63 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -18,6 +18,7 @@ ScrollableContainer::ScrollableContainer(Window* window) : GuiComponent{window} , mScrollPos{0.0f, 0.0f} , mScrollDir{0.0f, 0.0f} + , mClipSpacing{0.0f} , mAutoScrollDelay{0} , mAutoScrollSpeed{0} , mAutoScrollAccumulator{0} @@ -81,6 +82,13 @@ void ScrollableContainer::update(int deltaTime) float lineSpacing{mChildren.front()->getLineSpacing()}; float combinedHeight{mChildren.front()->getFont()->getHeight(lineSpacing)}; + // Calculate the spacing which will be used to clip the container. + if (lineSpacing > 1.2f && mClipSpacing == 0.0f) { + const float minimumSpacing = mChildren.front()->getFont()->getHeight(1.2f); + const float currentSpacing = mChildren.front()->getFont()->getHeight(lineSpacing); + mClipSpacing = std::round((currentSpacing - minimumSpacing) / 2.0f); + } + // Resize container to font height boundary to avoid rendering a fraction of the last line. if (!mUpdatedSize && contentSize.y > mSize.y) { float numLines{mSize.y / combinedHeight}; @@ -170,8 +178,13 @@ void ScrollableContainer::render(const glm::mat4& parentTrans) dimScaled.x = std::fabs(trans[3].x + mSize.x); dimScaled.y = std::fabs(trans[3].y + mSize.y); - glm::ivec2 clipDim{static_cast(dimScaled.x - trans[3].x), - static_cast(dimScaled.y - trans[3].y)}; + glm::ivec2 clipDim{static_cast(ceilf(dimScaled.x - trans[3].x)), + static_cast(ceilf(dimScaled.y - trans[3].y))}; + + // By effectively clipping the upper and lower boundaries of the container we mostly avoid + // scrolling outside the vertical starting and ending positions. + clipPos.y += static_cast(mClipSpacing); + clipDim.y -= static_cast(mClipSpacing); Renderer::pushClipRect(clipPos, clipDim); diff --git a/es-core/src/components/ScrollableContainer.h b/es-core/src/components/ScrollableContainer.h index 27b63f459..fb80b7eea 100644 --- a/es-core/src/components/ScrollableContainer.h +++ b/es-core/src/components/ScrollableContainer.h @@ -44,6 +44,7 @@ private: float mAutoScrollDelayConstant; float mAutoScrollSpeedConstant; float mResolutionModifier; + float mClipSpacing; int mAutoScrollDelay; int mAutoScrollSpeed; diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index bd0c40696..07eb7d36c 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -15,15 +15,16 @@ SliderComponent::SliderComponent( Window* window, float min, float max, float increment, const std::string& suffix) - : GuiComponent(window) - , mMin(min) - , mMax(max) - , mSingleIncrement(increment) - , mMoveRate(0) - , mKnob(window) - , mSuffix(suffix) + : GuiComponent{window} + , mMin{min} + , mMax{max} + , mSingleIncrement{increment} + , mMoveRate{0.0f} + , mBarHeight{0.0f} + , mKnob{window} + , mSuffix{suffix} { - assert((min - max) != 0); + assert((min - max) != 0.0f); // Some sane default value. mValue = (max + min) / 2.0f; @@ -41,7 +42,7 @@ bool SliderComponent::input(InputConfig* config, Input input) if (input.value) setValue(mValue - mSingleIncrement); - mMoveRate = input.value ? -mSingleIncrement : 0; + mMoveRate = input.value ? -mSingleIncrement : 0.0f; mMoveAccumulator = -MOVE_REPEAT_DELAY; return true; } @@ -49,13 +50,13 @@ bool SliderComponent::input(InputConfig* config, Input input) if (input.value) setValue(mValue + mSingleIncrement); - mMoveRate = input.value ? mSingleIncrement : 0; + mMoveRate = input.value ? mSingleIncrement : 0.0f; mMoveAccumulator = -MOVE_REPEAT_DELAY; return true; } } else { - mMoveRate = 0; + mMoveRate = 0.0f; } return GuiComponent::input(config, input); @@ -88,10 +89,9 @@ void SliderComponent::render(const glm::mat4& parentTrans) mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : 0.0f)}; - // Render line. - const float lineWidth{2.0f * Renderer::getScreenHeightModifier()}; - Renderer::drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f - lineWidth / 2.0f, width, - lineWidth, 0x777777FF, 0x777777FF); + // Render bar. + Renderer::drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f - mBarHeight / 2.0f, width, + mBarHeight, 0x777777FF, 0x777777FF); // Render knob. mKnob.render(trans); @@ -143,14 +143,28 @@ void SliderComponent::onValueChanged() mValueCache->metrics.size.x = textSize.x; // Fudge the width. } - // Update knob position/size. - mKnob.setResize(0, mSize.y * 0.7f); - float lineLength = + mKnob.setResize(0.0f, std::round(mSize.y * 0.7f)); + + float barLength = mSize.x - mKnob.getSize().x - (mValueCache ? mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : 0.0f); - mKnob.setPosition(((mValue - mMin / 2.0f) / mMax) * lineLength + mKnob.getSize().x / 2.0f, + int barHeight = static_cast(std::round(2.0f * Renderer::getScreenHeightModifier())); + + // For very low resolutions, make sure the bar height is not rounded to zero. + if (barHeight == 0) + barHeight = 1; + + // Resize the knob one pixel if necessary to keep the bar centered. + if (barHeight % 2 == 0 && static_cast(mKnob.getSize().y) % 2 != 0) + mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1.0f); + else if (barHeight == 1 && static_cast(mKnob.getSize().y) % 2 == 0) + mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1); + + mBarHeight = static_cast(barHeight); + + mKnob.setPosition(((mValue - mMin / 2.0f) / mMax) * barLength + mKnob.getSize().x / 2.0f, mSize.y / 2.0f); } diff --git a/es-core/src/components/SliderComponent.h b/es-core/src/components/SliderComponent.h index 1e6214bc8..9392364d9 100644 --- a/es-core/src/components/SliderComponent.h +++ b/es-core/src/components/SliderComponent.h @@ -45,6 +45,7 @@ private: float mValue; float mSingleIncrement; float mMoveRate; + float mBarHeight; int mMoveAccumulator; ImageComponent mKnob; diff --git a/es-core/src/components/SwitchComponent.cpp b/es-core/src/components/SwitchComponent.cpp index 23f64dd24..a2712ec5d 100644 --- a/es-core/src/components/SwitchComponent.cpp +++ b/es-core/src/components/SwitchComponent.cpp @@ -49,6 +49,16 @@ void SwitchComponent::render(const glm::mat4& parentTrans) renderChildren(trans); } +void SwitchComponent::setResize(float width, float height) +{ + // Reposition the switch after resizing to make it centered. + const glm::vec2 oldSize = mImage.getSize(); + mImage.setResize(width, height); + const float xDiff = oldSize.x - mImage.getSize().x; + const float yDiff = oldSize.y - mImage.getSize().y; + mImage.setPosition(mImage.getPosition().x + xDiff, mImage.getPosition().y + yDiff / 2.0f); +} + void SwitchComponent::setState(bool state) { mState = state; diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index c21219144..a0d1ac33c 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -22,7 +22,7 @@ public: void render(const glm::mat4& parentTrans) override; void onSizeChanged() override { mImage.setSize(mSize); } - void setResize(float width, float height) override { mImage.setResize(width, height); } + void setResize(float width, float height) override; bool getState() const { return mState; } void setState(bool state); diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index ad4dda89f..08aa17674 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -13,18 +13,18 @@ #include "utils/StringUtil.h" TextComponent::TextComponent(Window* window) - : GuiComponent(window) - , mFont(Font::get(FONT_SIZE_MEDIUM)) - , mColor(0x000000FF) - , mBgColor(0) - , mMargin(0.0f) - , mRenderBackground(false) - , mUppercase(false) - , mAutoCalcExtent(true, true) - , mHorizontalAlignment(ALIGN_LEFT) - , mVerticalAlignment(ALIGN_CENTER) - , mLineSpacing(1.5f) - , mNoTopMargin(false) + : GuiComponent{window} + , mFont{Font::get(FONT_SIZE_MEDIUM)} + , mColor{0x000000FF} + , mBgColor{0} + , mRenderBackground{false} + , mUppercase{false} + , mAutoCalcExtent{1, 1} + , mHorizontalAlignment{ALIGN_LEFT} + , mVerticalAlignment{ALIGN_CENTER} + , mLineSpacing{1.5f} + , mNoTopMargin{false} + , mSelectable{false} { } @@ -35,20 +35,19 @@ TextComponent::TextComponent(Window* window, Alignment align, glm::vec3 pos, glm::vec2 size, - unsigned int bgcolor, - float margin) - : GuiComponent(window) - , mFont(nullptr) - , mColor(0x000000FF) - , mBgColor(0) - , mMargin(margin) - , mRenderBackground(false) - , mUppercase(false) - , mAutoCalcExtent(true, true) - , mHorizontalAlignment(align) - , mVerticalAlignment(ALIGN_CENTER) - , mLineSpacing(1.5f) - , mNoTopMargin(false) + unsigned int bgcolor) + : GuiComponent{window} + , mFont{nullptr} + , mColor{0x000000FF} + , mBgColor{0} + , mRenderBackground{false} + , mUppercase{false} + , mAutoCalcExtent{1, 1} + , mHorizontalAlignment{align} + , mVerticalAlignment{ALIGN_CENTER} + , mLineSpacing{1.5f} + , mNoTopMargin{false} + , mSelectable{false} { setFont(font); setColor(color); @@ -236,12 +235,12 @@ void TextComponent::onTextChanged() // Abbreviate text. const std::string abbrev = "..."; glm::vec2 abbrevSize{f->sizeText(abbrev)}; - // mMargin adds a margin around the text if it's abbreviated. - float marginAdjustedSize = mSize.x - (mSize.x * mMargin); - while (text.size() && size.x + abbrevSize.x > marginAdjustedSize) { + while (text.size() && size.x + abbrevSize.x > mSize.x) { size_t newSize = Utils::String::prevCursor(text, text.size()); text.erase(newSize, text.size() - newSize); + if (!text.empty() && text.back() == ' ') + text.pop_back(); size = f->sizeText(text); } @@ -282,6 +281,14 @@ void TextComponent::setNoTopMargin(bool margin) onTextChanged(); } +std::vector TextComponent::getHelpPrompts() +{ + std::vector prompts; + if (mSelectable) + prompts.push_back(HelpPrompt("a", "select")); + return prompts; +} + void TextComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 57a5561c5..eb1694cdc 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -32,8 +32,7 @@ public: Alignment align = ALIGN_LEFT, glm::vec3 pos = {}, glm::vec2 size = {}, - unsigned int bgcolor = 0x00000000, - float margin = 0.0f); + unsigned int bgcolor = 0x00000000); void setFont(const std::shared_ptr& font); void setUppercase(bool uppercase); @@ -60,11 +59,15 @@ public: unsigned char getOpacity() const override { return mColor & 0x000000FF; } void setOpacity(unsigned char opacity) override; + void setSelectable(bool status) { mSelectable = status; } + virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; + virtual std::vector getHelpPrompts() override; + unsigned int getColor() const override { return mColor; } std::shared_ptr getFont() const override { return mFont; } Alignment getHorizontalAlignment() { return mHorizontalAlignment; } @@ -85,7 +88,6 @@ private: unsigned int mBgColor; unsigned char mColorOpacity; unsigned char mBgColorOpacity; - float mMargin; bool mRenderBackground; bool mUppercase; @@ -95,6 +97,7 @@ private: Alignment mVerticalAlignment; float mLineSpacing; bool mNoTopMargin; + bool mSelectable; }; #endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 35174aed0..c551ec9b8 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -24,7 +24,7 @@ struct TextListData { std::shared_ptr textCache; }; -// A graphical list. Supports multiple colors for rows and scrolling. +// A scrollable text list supporting multiple row colors. template class TextListComponent : public IList { protected: @@ -108,10 +108,10 @@ protected: virtual void onCursorChanged(const CursorState& state) override; private: - int mMarqueeOffset; - int mMarqueeOffset2; - int mMarqueeTime; - bool mMarqueeScroll; + int mLoopOffset; + int mLoopOffset2; + int mLoopTime; + bool mLoopScroll; Alignment mAlignment; float mHorizontalMargin; @@ -138,10 +138,10 @@ TextListComponent::TextListComponent(Window* window) : IList(window) , mSelectorImage(window) { - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; - mMarqueeTime = 0; - mMarqueeScroll = false; + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + mLoopScroll = false; mHorizontalMargin = 0.0f; mAlignment = ALIGN_CENTER; @@ -223,8 +223,10 @@ template void TextListComponent::render(const glm::mat4& parentT dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y; Renderer::pushClipRect( - glm::ivec2{static_cast(trans[3].x + mHorizontalMargin), static_cast(trans[3].y)}, - glm::ivec2{static_cast(dim.x - mHorizontalMargin * 2.0f), static_cast(dim.y)}); + glm::ivec2{static_cast(std::round(trans[3].x + mHorizontalMargin)), + static_cast(std::round(trans[3].y))}, + glm::ivec2{static_cast(std::round(dim.x - mHorizontalMargin * 2.0f)), + static_cast(std::round(dim.y))}); for (int i = startEntry; i < listCutoff; i++) { typename IList::Entry& entry = mEntries.at(static_cast(i)); @@ -271,26 +273,26 @@ template void TextListComponent::render(const glm::mat4& parentT // Render text. glm::mat4 drawTrans{trans}; - // Currently selected item text might be scrolling. - if (mCursor == i && mMarqueeOffset > 0) + // Currently selected item text might be looping. + if (mCursor == i && mLoopOffset > 0) drawTrans = glm::translate( - drawTrans, offset - glm::vec3{static_cast(mMarqueeOffset), 0.0f, 0.0f}); + drawTrans, offset - glm::vec3{static_cast(mLoopOffset), 0.0f, 0.0f}); else drawTrans = glm::translate(drawTrans, offset); // Needed to avoid flickering when returning to the start position. - if (mMarqueeOffset == 0 && mMarqueeOffset2 == 0) - mMarqueeScroll = false; + if (mLoopOffset == 0 && mLoopOffset2 == 0) + mLoopScroll = false; Renderer::setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); - // Render currently selected row again if marquee is scrolled far enough for it to repeat. - if ((mCursor == i && mMarqueeOffset2 < 0) || (mCursor == i && mMarqueeScroll)) { - mMarqueeScroll = true; + // Render currently selected row again if text is moved far enough for it to repeat. + if ((mCursor == i && mLoopOffset2 < 0) || (mCursor == i && mLoopScroll)) { + mLoopScroll = true; drawTrans = trans; drawTrans = glm::translate( - drawTrans, offset - glm::vec3{static_cast(mMarqueeOffset2), 0.0f, 0.0f}); + drawTrans, offset - glm::vec3{static_cast(mLoopOffset2), 0.0f, 0.0f}); Renderer::setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); } @@ -353,11 +355,11 @@ template void TextListComponent::update(int deltaTime) stopScrolling(); if (!isScrolling() && size() > 0) { - // Always reset the marquee offsets. - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; + // Always reset the loop offsets. + mLoopOffset = 0; + mLoopOffset2 = 0; - // If we're not scrolling and this object's text exceeds our size, then marquee it. + // If we're not scrolling and this object's text exceeds our size, then loop it. const float textLength = mFont ->sizeText(Utils::String::toUpper( mEntries.at(static_cast(mCursor)).name)) @@ -374,16 +376,16 @@ template void TextListComponent::update(int deltaTime) const float returnTime = (returnLength * 1000.0f) / speed; const int maxTime = static_cast(delay + scrollTime + returnTime); - mMarqueeTime += deltaTime; - while (mMarqueeTime > maxTime) - mMarqueeTime -= maxTime; + mLoopTime += deltaTime; + while (mLoopTime > maxTime) + mLoopTime -= maxTime; - mMarqueeOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, - static_cast(mMarqueeTime), - scrollLength + returnLength)); + mLoopOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, + static_cast(mLoopTime), + scrollLength + returnLength)); - if (mMarqueeOffset > (scrollLength - (limit - returnLength))) - mMarqueeOffset2 = static_cast(mMarqueeOffset - (scrollLength + returnLength)); + if (mLoopOffset > (scrollLength - (limit - returnLength))) + mLoopOffset2 = static_cast(mLoopOffset - (scrollLength + returnLength)); } } @@ -405,9 +407,9 @@ void TextListComponent::add(const std::string& name, const T& obj, unsigned i template void TextListComponent::onCursorChanged(const CursorState& state) { - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; - mMarqueeTime = 0; + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; if (mCursorChangedCallback) mCursorChangedCallback(state); diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index a3ce28161..8a1c270f3 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -567,8 +567,8 @@ void GuiTextEditKeyboardPopup::shiftKeys() mShift = !mShift; if (mShift) { - mShiftButton->setFlatColorFocused(0xFF2222FF); - mShiftButton->setFlatColorUnfocused(0xFF2222FF); + mShiftButton->setFlatColorFocused(0xF26767FF); + mShiftButton->setFlatColorUnfocused(0xF26767FF); } else { mShiftButton->setFlatColorFocused(0x878787FF); @@ -600,8 +600,8 @@ void GuiTextEditKeyboardPopup::altKeys() mAlt = !mAlt; if (mAlt) { - mAltButton->setFlatColorFocused(0xFF2222FF); - mAltButton->setFlatColorUnfocused(0xFF2222FF); + mAltButton->setFlatColorFocused(0xF26767FF); + mAltButton->setFlatColorUnfocused(0xF26767FF); } else { mAltButton->setFlatColorFocused(0x878787FF); diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 70375c5b3..9537b25d7 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -73,8 +73,8 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) mSourceHeight = svgImage->height; } - mWidth = static_cast(floorf(floorf(mSourceWidth) * mScaleDuringLoad)); - mHeight = static_cast(floorf(floorf(mSourceHeight) * mScaleDuringLoad)); + mWidth = static_cast(std::round(mSourceWidth * mScaleDuringLoad)); + mHeight = static_cast(std::round(mSourceHeight * mScaleDuringLoad)); if (mWidth == 0) { // Auto scale width to keep aspect ratio. @@ -92,9 +92,8 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) NSVGrasterizer* rast = nsvgCreateRasterizer(); - nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), - static_cast(mWidth), static_cast(mHeight), - static_cast(mWidth) * 4); + nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, + mHeight, mWidth * 4); // This is important in order to avoid memory leaks. nsvgDeleteRasterizer(rast); @@ -148,8 +147,8 @@ bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size mDataRGBA.reserve(width * height * 4); mDataRGBA.insert(mDataRGBA.begin(), dataRGBA, dataRGBA + (width * height * 4)); - mWidth = width; - mHeight = height; + mWidth = static_cast(width); + mHeight = static_cast(height); return true; } diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index c50cd19d3..0e76d8159 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -72,8 +72,8 @@ private: std::string mPath; unsigned int mTextureID; std::vector mDataRGBA; - size_t mWidth; - size_t mHeight; + int mWidth; + int mHeight; float mSourceWidth; float mSourceHeight; float mScaleDuringLoad; diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 74e5d5bfe..b6f7b8d8a 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -194,7 +194,7 @@ std::shared_ptr TextureResource::get(const std::string& path, return tex; } -void TextureResource::rasterizeAt(size_t width, size_t height) +void TextureResource::rasterizeAt(float width, float height) { if (mTextureData != nullptr) { glm::vec2 textureSize = mTextureData.get()->getSize(); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 223d60fcc..91ba5ef7e 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -44,7 +44,7 @@ public: // It does unload and re-rasterize the texture though which may cause flickering in some // situations. An alternative is to set a scaling factor directly when loading the texture // using get(), by using the scaleDuringLoad parameter (which also works for raster graphics). - void rasterizeAt(size_t width, size_t height); + void rasterizeAt(float width, float height); glm::vec2 getSourceImageSize() const { return mSourceSize; } virtual ~TextureResource(); diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index 0a07c9872..f8a9d5442 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -8,8 +8,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" - viewBox="0 0 21.166666 29.633334" + height="92" + viewBox="0 0 21.166666 24.341667" version="1.1" id="svg4842" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" @@ -23,11 +23,11 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="12.442154" - inkscape:cx="-18.41824" - inkscape:cy="59.681612" + inkscape:zoom="12.066296" + inkscape:cx="11.393424" + inkscape:cy="66.081732" inkscape:document-units="mm" - inkscape:current-layer="layer2" + inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="3840" inkscape:window-height="2065" @@ -59,155 +59,12 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-1.9829021e-4,-266.11715)"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + transform="translate(-1.9829021e-4,-271.40882)"> + style="fill:#ffffff;stroke-width:0.25911999"> + + id="g16" + transform="matrix(0.10079384,0,0,0.08235571,-2.2547839e-4,271.45845)" + style="fill:#d7d7d7;fill-opacity:1"> + + + + + + + + + @@ -380,7 +282,7 @@ + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.21396799;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" /> - - + aria-label="ALTERNATIVE" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.60125422px;line-height:1.25;font-family:FontAwesome;-inkscape-font-specification:FontAwesome;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="text4942" + transform="matrix(1.1491297,0,0,1.5857309,-1.3267257,-15.489625)"> + d="m 3.9524717,26.040316 q 0,-0.01016 0.012066,-0.07938 0.033659,-0.208938 0.076844,-0.432484 0.043185,-0.22418 0.1117727,-0.555688 0.068588,-0.332142 0.089545,-0.435659 0.1270144,-0.0057 0.3492895,-0.0057 0.2070334,0 0.3619909,0.0057 0.010161,0 0.019052,0.0076 0.00889,0.007 0.010796,0.01778 0.027943,0.141621 0.094626,0.462967 0.067318,0.321347 0.1143129,0.56839 0.04763,0.246408 0.081289,0.467413 -0.1200285,0.0057 -0.221005,0.0057 -0.1619433,0 -0.2489481,-0.0057 -0.010796,-6.35e-4 -0.019687,-0.0076 -0.00826,-0.0076 -0.010161,-0.01778 -0.028578,-0.146067 -0.04636,-0.254029 H 4.4922827 q -0.020322,0.135905 -0.0489,0.279432 -0.1219338,0.0057 -0.2102088,0.0057 -0.1054219,0 -0.2597444,-0.0057 -0.00953,0 -0.015242,-0.0057 -0.00572,-0.0064 -0.00572,-0.01524 z m 0.5823608,-0.588711 h 0.1428912 q -0.015242,-0.104152 -0.04128,-0.296579 -0.026038,-0.193062 -0.037469,-0.273081 -0.012701,0.103517 -0.033659,0.297214 -0.020957,0.193062 -0.030483,0.272446 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4944" + inkscape:connector-curvature="0" /> + d="m 5.3655065,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206636,-0.0057 0.2222751,-0.0057 0.1047869,0 0.2603795,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.010161,0.477574 0.010161,0.736683 0,0.168929 -0.00191,0.329603 h 0.010796 q 0.2318012,0 0.3873938,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1206637,0.0057 -0.3746924,0.0057 -0.062872,0 -0.2680003,-0.0044 -0.2044932,-0.0038 -0.2718108,-0.0063 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4946" + inkscape:connector-curvature="0" /> + d="m 6.2012611,24.730163 q 0,-0.102247 0.00254,-0.193062 0.1212988,-0.0057 0.5880766,-0.0057 0.3689767,0 0.5245693,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.143526 0.00254,0.215924 0,0.103517 -0.00254,0.190522 -0.04128,0.0019 -0.3073748,0.0044 0.00254,0.137175 0.00254,0.325791 0,0.355641 -0.010161,0.762087 -0.1206636,0.0057 -0.2222751,0.0057 -0.1047869,0 -0.2603795,-0.0057 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 0,-0.117488 0.00254,-0.328332 -0.1460665,-0.0032 -0.2819719,-0.007 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.00254,-0.146067 -0.00254,-0.208304 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4948" + inkscape:connector-curvature="0" /> + d="m 7.4485421,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206637,-0.0057 0.5271097,-0.0057 0.2876875,0 0.4432801,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1073272,0.0051 -0.4039057,0.0051 -0.053346,0 -0.096531,-6.35e-4 -0.00127,0.08764 -0.00191,0.133365 0.2273557,0.0013 0.3041994,0.0044 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00191,0.104152 0.00191,0.191157 0,0.07811 -0.00191,0.169564 H 7.9521541 q 6.35e-4,0.04445 0.00191,0.132095 h 0.030483 q 0.3111852,0 0.4496309,0.0051 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1206637,0.0057 -0.4356593,0.0057 -0.062872,0 -0.2680003,-0.0044 -0.2044932,-0.0038 -0.2718108,-0.0063 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4950" + inkscape:connector-curvature="0" /> + d="m 8.6069129,25.266164 q 0,-0.127015 0.00254,-0.35183 0.00254,-0.224816 0.00254,-0.35183 0.1270144,-0.01969 0.2699055,-0.03112 0.1435263,-0.01207 0.2508534,-0.01207 0.092721,0 0.1555926,0.0032 0.062872,0.0025 0.1352703,0.0127 0.073033,0.0095 0.1200286,0.02794 0.046995,0.01842 0.093991,0.05081 0.046995,0.03239 0.073033,0.07875 0.026673,0.04636 0.043185,0.113678 0.016512,0.06732 0.016512,0.154322 0,0.137811 -0.071128,0.242598 -0.071128,0.104786 -0.1981424,0.164483 0.08637,0.02985 0.1403509,0.121934 0.1263793,0.248313 0.2019528,0.571565 -0.1168532,0.01397 -0.2660951,0.01397 -0.1079622,0 -0.2038581,-0.0064 -0.012066,-6.35e-4 -0.023498,-0.0076 -0.011431,-0.007 -0.015242,-0.01778 -0.123839,-0.375963 -0.1924268,-0.607129 h -0.033659 q 0.00127,0.07113 0.00572,0.291498 0.00508,0.22037 0.00572,0.333413 -0.1041518,0.0044 -0.2229102,0.0044 -0.1073272,0 -0.2597444,-0.0044 -0.010796,0 -0.018417,-0.007 -0.00699,-0.0076 -0.00699,-0.01842 0,-0.117489 -0.00254,-0.379138 -0.00254,-0.26165 -0.00254,-0.390569 z m 0.5004366,-0.142256 h 0.043185 q 0.060332,0 0.097166,-0.02985 0.037469,-0.03048 0.037469,-0.08002 0,-0.06224 -0.025403,-0.09209 -0.025403,-0.03048 -0.09018,-0.03048 -0.033024,0 -0.060967,0.0019 -0.00127,0.148607 -0.00127,0.230532 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4952" + inkscape:connector-curvature="0" /> + d="m 9.9380235,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206635,-0.0057 0.2095735,-0.0057 0.07557,0 0.219735,0.0057 0.01842,6.35e-4 0.02794,0.0254 l 0.26292,0.650314 -0.0089,-0.675717 q 0.121299,-0.0057 0.222275,-0.0057 0.09907,0 0.234977,0.0057 0.0108,6.35e-4 0.01778,0.0083 0.0076,0.007 0.0076,0.01715 0.01016,0.477574 0.01016,0.736683 0,0.355641 -0.01016,0.762087 -0.105422,0.0057 -0.196873,0.0057 -0.108597,0 -0.232436,-0.0057 -0.01905,-6.35e-4 -0.02794,-0.0254 l -0.26292,-0.650314 0.0089,0.675717 q -0.120663,0.0057 -0.209573,0.0057 -0.100342,0 -0.2476786,-0.0057 -0.010796,-6.35e-4 -0.018417,-0.0076 -0.00699,-0.0076 -0.00699,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4954" + inkscape:connector-curvature="0" /> + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index a1d7dbc3c..9e631adf0 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -10,8 +10,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" - viewBox="0 0 21.166666 29.633334" + height="92" + viewBox="0 0 21.166666 24.341667" version="1.1" id="svg4842" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="9.5782066" - inkscape:cx="1.1766084" - inkscape:cy="50.443327" + inkscape:zoom="11.584182" + inkscape:cx="-13.360205" + inkscape:cy="53.651595" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -49,7 +49,7 @@ image/svg+xml - + @@ -57,17 +57,17 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-1.9829021e-4,-266.11715)"> + transform="translate(-1.9829021e-4,-271.40882)"> + height="24.341667" + x="0.0001986081" + y="271.40881" /> + transform="matrix(0.10079384,0,0,0.0816046,-2.2531965e-4,271.54688)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> - - - - - - - - - - - - - - - - - - - - - - - + y="276.70892" + x="1.8134596" /> diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index a09e55f4c..376d4f21b 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -7,10 +7,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="80.000008" - height="112" + width="80" + height="92" version="1.1" - viewBox="0 0 21.166668 29.633334" + viewBox="0 0 21.166666 24.341667" id="svg92" sodipodi:docname="badge_completed.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -33,9 +33,9 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:zoom="12.442155" - inkscape:cx="-34.776172" - inkscape:cy="67.39851" + inkscape:zoom="9.6754505" + inkscape:cx="14.68785" + inkscape:cy="65.424484" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -54,15 +54,15 @@ + height="24.341667" + x="2.2368853e-07" + y="-2.6645353e-15" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.2317622e-4,0.13809161)" + style="fill:#0e0e0e;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#0e0e0e;fill-opacity:1" /> - - - - - - - - - - - - - - - - - - - - - - - + y="5.3001137" + x="1.8323119" /> + transform="matrix(0.09207517,0,0,0.11015111,-4.2465753e-4,-1.9389228e-4)"> diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 4b8236cae..913880a4b 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -8,9 +8,9 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" + height="92" version="1.1" - viewBox="0 0 21.166666 29.633332" + viewBox="0 0 21.166666 24.341666" id="svg90" sodipodi:docname="badge_favorite.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -30,9 +30,9 @@ id="namedview92" showgrid="false" units="px" - inkscape:zoom="5.4859695" - inkscape:cx="-51.316674" - inkscape:cy="71.543477" + inkscape:zoom="12.442154" + inkscape:cx="-26.425635" + inkscape:cy="40.73358" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -51,15 +51,15 @@ + y="-2.6253017e-14" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.2392733e-4,0.13809204)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> + d="m 17.240062,12.456484 -4.709693,-0.491108 -1.927985,-4.2699571 -1.9279844,4.2699571 -4.7096929,0.491108 3.5178058,3.130612 -0.9820949,4.573603 4.1020064,-2.335917 4.102007,2.335917 -0.982095,-4.573603 z" /> + transform="matrix(0.10111638,0,0,0.09920503,-4.2392733e-4,-1.9532637e-4)"> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index 565806a74..04c066613 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -7,10 +7,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="80.000008" - height="112" + width="80" + height="92" version="1.1" - viewBox="0 0 21.166668 29.633334" + viewBox="0 0 21.166666 24.341667" id="svg90" sodipodi:docname="badge_kidgame.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -33,9 +33,9 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:zoom="8.2057888" - inkscape:cx="-57.797153" - inkscape:cy="68.015272" + inkscape:zoom="9.0242459" + inkscape:cx="-20.683876" + inkscape:cy="60.045111" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -54,15 +54,15 @@ + height="24.341667" + x="2.2368853e-07" + y="-2.6645353e-15" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.237873e-4,0.13809261)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> - - - - - - - - - - - - - - - - - - - - - - - + y="5.3001156" + x="1.8132613" /> + d="M 4.2719194,9.2899832 7.5642988,20.858601 16.894338,18.490114 16.025104,15.486921 10.530737,16.910518 10.155183,15.577444 15.650846,14.180192 14.791009,11.17484 9.310487,12.587467 8.9381592,11.320213 14.438408,9.9434334 13.590192,6.9470637 Z" /> + transform="matrix(0.09733762,0,0,0.10419427,-4.2465753e-4,-1.9389228e-4)"> + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="567.500px" + height="90.32px" viewBox="0 0 567.500 90.32" enable-background="new 0 0 566.932 90.32" xml:space="preserve"> diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 2f20ddca6..690d7f0c4 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -237,8 +237,8 @@ based on: 'recalbox-multi' by the Recalbox community right - 0.8125 0.675 - 0.15 0.21 + 0.815 0.675 + 0.13 0.1635 0 0 left 3