diff --git a/USERGUIDE.md b/USERGUIDE.md index 15e7fd905..2232b83a9 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -778,6 +778,10 @@ Whether to sort your favorite games above your other games in the gamelists. With this setting enabled, there is a star symbol added at the beginning of the game name in the gamelist views. It's strongly recommended to keep this setting enabled if the option to sort favorite games above non-favorites has been enabled. If not, favorite games would be sorted on top of the gamelist with no visual indication that they are favorites, which would be very confusing. +**Enable quick list scrolling overlay** + +With this option enabled, there will be an overlay displayed when scrolling the gamelists quickly, i.e. when holding down the _Up_, _Down_, _Left shoulder_ or _Right shoulder_ buttons for some time. The overlay will darken the background slightly and display the first two characters of the game name. If the game is a favorite and the setting to sort favorites above non-favorites has been enabled, a star will be shown instead. + **Enable shortcut to toggle favorites** This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. For additional restrictions, the application can be set to Kid or Kiosk mode as is explained [elsewhere](USERGUIDE.md#ui-modes) in this guide. Note that this setting does not affect the functionality to use the _Y_ button to add games to custom collections. diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index e86e4f5aa..dc580e31d 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -374,6 +374,18 @@ void GuiMenu::openUISettings() } }); + // Enable quick list scrolling overlay. + auto list_scroll_overlay = std::make_shared(mWindow); + list_scroll_overlay->setState(Settings::getInstance()->getBool("ListScrollOverlay")); + s->addWithLabel("ENABLE QUICK LIST SCROLLING OVERLAY", list_scroll_overlay); + s->addSaveFunc([list_scroll_overlay, s] { + if (list_scroll_overlay->getState() != + Settings::getInstance()->getBool("ListScrollOverlay")) { + Settings::getInstance()->setBool("ListScrollOverlay", list_scroll_overlay->getState()); + s->setNeedsSaving(); + } + }); + // Enable the 'Y' button for tagging games as favorites. auto favorites_add_button = std::make_shared(mWindow); favorites_add_button->setState(Settings::getInstance()->getBool("FavoritesAddButton")); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index a9fe64503..c2eaf79c2 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -121,6 +121,7 @@ void Settings::setDefaults() mBoolMap["FoldersOnTop"] = { true, true }; mBoolMap["FavoritesFirst"] = { true, true }; mBoolMap["FavoritesStar"] = { true, true }; + mBoolMap["ListScrollOverlay"] = { false, false }; mBoolMap["FavoritesAddButton"] = { true, true }; mBoolMap["GamelistFilters"] = { true, true }; mBoolMap["QuickSystemSelect"] = { true, true }; diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index f2c21fa2c..c607ba986 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -36,7 +36,8 @@ Window::Window() mCachedBackground(false), mInvalidatedCachedBackground(false), mTopOpacity(0), - mTopScale(0.5) + mTopScale(0.5), + mListScrollOpacity(0) { mHelp = new HelpComponent(this); mBackgroundOverlay = new ImageComponent(this); @@ -113,6 +114,8 @@ bool Window::init() mBackgroundOverlay->setResize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); + mListScrollFont = Font::get(FONT_SIZE_LARGE); + // Update our help because font sizes probably changed. if (peekGui()) peekGui()->updateHelpPrompts(); @@ -399,6 +402,22 @@ void Window::render() } } + if (mListScrollOpacity != 0) { + Renderer::setMatrix(Transform4x4f::Identity()); + Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), + static_cast(Renderer::getScreenHeight()), + 0x00000000 | mListScrollOpacity, 0x00000000 | mListScrollOpacity); + + Vector2f offset = mListScrollFont->sizeText(mListScrollText); + offset[0] = (Renderer::getScreenWidth() - offset.x()) * 0.5f; + offset[1] = (Renderer::getScreenHeight() - offset.y()) * 0.5f; + + TextCache* cache = mListScrollFont->buildTextCache(mListScrollText, + offset.x(), offset.y(), 0xFFFFFF00 | mListScrollOpacity); + mListScrollFont->renderTextCache(cache); + delete cache; + } + if (!mRenderedHelpPrompts) mHelp->render(transform); @@ -481,6 +500,12 @@ void Window::renderLoadingScreen(std::string text) Renderer::swapBuffers(); } +void Window::renderListScrollOverlay(unsigned char opacity, const std::string& text) +{ + mListScrollOpacity = static_cast(opacity * 0.6f); + mListScrollText = text; +} + void Window::renderHelpPromptsEarly() { mHelp->render(Transform4x4f::Identity()); diff --git a/es-core/src/Window.h b/es-core/src/Window.h index 259e41b6e..59197fda5 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -81,13 +81,23 @@ public: void setAllowSleep(bool sleep); void renderLoadingScreen(std::string text); + // The list scroll overlay is triggered from IList when the highest scrolling tier is reached. + void renderListScrollOverlay(unsigned char opacity, const std::string& text); void renderHelpPromptsEarly(); // Used to render HelpPrompts before a fade. void setHelpPrompts(const std::vector& prompts, const HelpStyle& style); void setScreensaver(Screensaver* screensaver) { mScreensaver = screensaver; } - void setInfoPopup(InfoPopup* infoPopup) { delete mInfoPopup; mInfoPopup = infoPopup; } - inline void stopInfoPopup() { if (mInfoPopup) mInfoPopup->stop(); }; + void setInfoPopup(InfoPopup* infoPopup) + { + delete mInfoPopup; + mInfoPopup = infoPopup; + } + void stopInfoPopup() + { + if (mInfoPopup) + mInfoPopup->stop(); + }; bool isScreensaverActive() { return mRenderScreensaver; }; void startScreensaver(); @@ -97,12 +107,16 @@ public: void setLaunchedGame(); void unsetLaunchedGame(); + bool getGameLaunchedState() { return mGameLaunchedState; }; void setAllowTextScrolling(bool setting) { mAllowTextScrolling = setting; }; bool getAllowTextScrolling() { return mAllowTextScrolling; }; void invalidateCachedBackground() - { mCachedBackground = false; mInvalidatedCachedBackground = true;}; + { + mCachedBackground = false; + mInvalidatedCachedBackground = true; + }; private: void onSleep(); @@ -120,6 +134,10 @@ private: std::vector> mDefaultFonts; std::unique_ptr mFrameDataText; + std::string mListScrollText; + std::shared_ptr mListScrollFont; + unsigned char mListScrollOpacity; + bool mNormalizeNextUpdate; int mFrameTimeElapsed; int mFrameCountElapsed; diff --git a/es-core/src/components/IList.h b/es-core/src/components/IList.h index 06087139a..79d61589b 100644 --- a/es-core/src/components/IList.h +++ b/es-core/src/components/IList.h @@ -3,14 +3,14 @@ // EmulationStation Desktop Edition // IList.h // -// Gamelist base class. +// List base class, used by both the gamelist views and the menus. // #ifndef ES_CORE_COMPONENTS_ILIST_H #define ES_CORE_COMPONENTS_ILIST_H #include "components/ImageComponent.h" -#include "resources/Font.h" +#include "utils/StringUtil.h" #include "Window.h" enum CursorState { @@ -38,11 +38,10 @@ struct ScrollTierList { const ScrollTier QUICK_SCROLL_TIERS[] = { {500, 500}, {2000, 114}, - {4000, 32}, {0, 16} }; const ScrollTierList LIST_SCROLL_STYLE_QUICK = { - 4, + 3, QUICK_SCROLL_TIERS }; @@ -51,7 +50,10 @@ const ScrollTier SLOW_SCROLL_TIERS[] = { {0, 200} }; -const ScrollTierList LIST_SCROLL_STYLE_SLOW = { 2, SLOW_SCROLL_TIERS }; +const ScrollTierList LIST_SCROLL_STYLE_SLOW = { + 2, + SLOW_SCROLL_TIERS +}; template class IList : public GuiComponent @@ -72,8 +74,6 @@ protected: unsigned char mTitleOverlayOpacity; unsigned int mTitleOverlayColor; - ImageComponent mGradient; - std::shared_ptr mTitleOverlayFont; const ScrollTierList& mTierList; const ListLoopType mLoopType; @@ -87,7 +87,6 @@ public: const ScrollTierList& tierList = LIST_SCROLL_STYLE_QUICK, const ListLoopType& loopType = LIST_PAUSE_AT_END) : GuiComponent(window), - mGradient(window), mTierList(tierList), mLoopType(loopType), mWindow(window) @@ -100,10 +99,6 @@ public: mTitleOverlayOpacity = 0x00; mTitleOverlayColor = 0xFFFFFF00; - mGradient.setResize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); - mGradient.setImage(":/graphics/scroll_gradient.png"); - mTitleOverlayFont = Font::get(FONT_SIZE_LARGE); } bool isScrolling() const @@ -118,6 +113,10 @@ public: void stopScrolling() { +// if (mScrollTier >= 2) +// NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + mTitleOverlayOpacity = 0; + listInput(0); if (mScrollVelocity == 0) onCursorChanged(CURSOR_STOPPED); @@ -212,7 +211,10 @@ public: return false; } - inline int size() const { return static_cast(mEntries.size()); } + inline int size() const + { + return static_cast(mEntries.size()); + } protected: void remove(typename std::vector::const_iterator& it) @@ -259,8 +261,8 @@ protected: // Update the title overlay opacity. // Fade in if scroll tier is >= 1, otherwise fade out. const int dir = (mScrollTier >= mTierList.count - 1) ? 1 : -1; - // We just do a 1-to-1 time -> opacity, no scaling. - int op = mTitleOverlayOpacity + deltaTime*dir; + // We simply translate the time directly to opacity, i.e. no scaling is performed. + int op = mTitleOverlayOpacity + deltaTime * dir; if (op >= 255) mTitleOverlayOpacity = 255; else if (op <= 0) @@ -283,7 +285,7 @@ protected: scrollCount++; } - // Are we ready to go even FASTER? + // Should we go to the next scrolling tier? while (mScrollTier < mTierList.count - 1 && mScrollTierAccumulator >= mTierList.tiers[mScrollTier].length) { mScrollTierAccumulator -= mTierList.tiers[mScrollTier].length; @@ -297,28 +299,43 @@ protected: void listRenderTitleOverlay(const Transform4x4f& /*trans*/) { - if (size() == 0 || !mTitleOverlayFont || mTitleOverlayOpacity == 0) + if (!Settings::getInstance()->getBool("ListScrollOverlay")) return; - // We don't bother caching this because it's only two letters and will change pretty - // much every frame if we're scrolling. - const std::string text = getSelectedName().size() >= 2 ? - getSelectedName().substr(0, 2) : "??"; + if (size() == 0 || mTitleOverlayOpacity == 0) { + mWindow->renderListScrollOverlay(0, ""); + return; + } - Vector2f off = mTitleOverlayFont->sizeText(text); - off[0] = (Renderer::getScreenWidth() - off.x()) * 0.5f; - off[1] = (Renderer::getScreenHeight() - off.y()) * 0.5f; + std::string titleIndex; + bool favoritesSorting; - Transform4x4f identTrans = Transform4x4f::Identity(); + if (getSelected()->getSystem()->isCustomCollection()) + favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom"); + else + favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); - mGradient.setOpacity(mTitleOverlayOpacity); - mGradient.render(identTrans); + if (favoritesSorting && getSelected()->getFavorite()) { + #if defined(_MSC_VER) // MSVC compiler. + titleIndex = Utils::String::wideStringToString(L"\uF005"); + #else + titleIndex = "\uF005"; + #endif + } + else { + titleIndex = getSelected()->getName(); + if (titleIndex.size()) { + titleIndex[0] = toupper(titleIndex[0]); + if (titleIndex.size() > 1) { + titleIndex = titleIndex.substr(0, 2); + titleIndex[1] = tolower(titleIndex[1]); + } + } + } - TextCache* cache = mTitleOverlayFont->buildTextCache(text, off.x(), off.y(), - 0xFFFFFF00 | mTitleOverlayOpacity); - // Relies on mGradient's render for Renderer::setMatrix() - mTitleOverlayFont->renderTextCache(cache); - delete cache; + // The actual rendering takes place in Window to make sure that the overlay is placed on + // top of all GUI elements but below the info popups and GPU statistics overlay. + mWindow->renderListScrollOverlay(mTitleOverlayOpacity, titleIndex); } void scroll(int amt) diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 5b915d9d4..3f38a7fae 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -3,7 +3,7 @@ // EmulationStation Desktop Edition // TextListComponent.h // -// Used for displaying and navigating the gamelists. +// Text list used for displaying and navigating the gamelist views. // #ifndef ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H @@ -11,6 +11,7 @@ #include "components/IList.h" #include "math/Misc.h" +#include "resources/Font.h" #include "utils/StringUtil.h" #include "Log.h" #include "Sound.h" @@ -93,8 +94,11 @@ public: inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; } protected: - virtual void onScroll() override { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); } + virtual void onScroll() override + { + if (!NavigationSounds::getInstance()->isPlayingThemeNavigationSound(SCROLLSOUND)) + NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + } virtual void onCursorChanged(const CursorState& state) override; private: