diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 44f7c4158..6f3292f07 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -294,6 +294,101 @@ void GuiMenu::openUIOptions() themeAspectRatiosFunc(Settings::getInstance()->getString("ThemeSet"), Settings::getInstance()->getString("ThemeAspectRatio")); + // Theme transition animations. + auto themeTransitionAnimations = std::make_shared>( + getHelpStyle(), "THEME TRANSITION ANIMS", false); + std::string selectedAnim {Settings::getInstance()->getString("ThemeTransitionAnimations")}; + themeTransitionAnimations->add("AUTOMATIC", "automatic", selectedAnim == "automatic"); + // If there are no objects returned, then there must be a manually modified entry in the + // configuration file. Simply set the animation type to "automatic" in this case. + if (themeTransitionAnimations->getSelectedObjects().size() == 0) + themeTransitionAnimations->selectEntry(0); + s->addWithLabel("THEME TRANSITION ANIMATIONS", themeTransitionAnimations); + s->addSaveFunc([themeTransitionAnimations, s] { + if (themeTransitionAnimations->getSelected() != + Settings::getInstance()->getString("ThemeTransitionAnimations")) { + Settings::getInstance()->setString("ThemeTransitionAnimations", + themeTransitionAnimations->getSelected()); + ThemeData::setThemeTransitions(); + s->setNeedsSaving(); + } + }); + + auto themeTransitionAnimationsFunc = [=](const std::string& selectedTheme, + const std::string& selectedTransitionAnimation) { + std::map::const_iterator + currentSet {themeSets.find(selectedTheme)}; + if (currentSet == themeSets.cend()) + return; + // We need to recreate the OptionListComponent entries. + themeTransitionAnimations->clearEntries(); + if (currentSet->second.capabilities.legacyTheme) { + themeTransitionAnimations->add("Legacy theme set", "automatic", true); + themeTransitionAnimations->setEnabled(false); + themeTransitionAnimations->setOpacity(DISABLED_OPACITY); + themeTransitionAnimations->getParent() + ->getChild(themeTransitionAnimations->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + } + else { + themeTransitionAnimations->add("AUTOMATIC", "automatic", + "automatic" == selectedTransitionAnimation); + if (currentSet->second.capabilities.transitions.size() == 1 && + currentSet->second.capabilities.transitions.front().selectable) { + std::string label; + if (currentSet->second.capabilities.transitions.front().label == "") + label = "THEME PROFILE"; + else + label = currentSet->second.capabilities.transitions.front().label; + const std::string transitionAnim { + currentSet->second.capabilities.transitions.front().name}; + themeTransitionAnimations->add(label, transitionAnim, + transitionAnim == selectedTransitionAnimation); + } + else { + for (size_t i {0}; i < currentSet->second.capabilities.transitions.size(); ++i) { + if (!currentSet->second.capabilities.transitions[i].selectable) + continue; + std::string label; + if (currentSet->second.capabilities.transitions[i].label == "") + label = "THEME PROFILE " + std::to_string(i + 1); + else + label = currentSet->second.capabilities.transitions[i].label; + const std::string transitionAnim { + currentSet->second.capabilities.transitions[i].name}; + themeTransitionAnimations->add(label, transitionAnim, + transitionAnim == selectedTransitionAnimation); + } + } + if (std::find(currentSet->second.capabilities.suppressedTransitionEntries.cbegin(), + currentSet->second.capabilities.suppressedTransitionEntries.cend(), + "builtin-instant") == + currentSet->second.capabilities.suppressedTransitionEntries.cend()) { + themeTransitionAnimations->add("INSTANT (BUILT-IN)", "builtin-instant", + "builtin-instant" == selectedTransitionAnimation); + } + if (std::find(currentSet->second.capabilities.suppressedTransitionEntries.cbegin(), + currentSet->second.capabilities.suppressedTransitionEntries.cend(), + "builtin-slide") == + currentSet->second.capabilities.suppressedTransitionEntries.cend()) { + themeTransitionAnimations->add("SLIDE (BUILT-IN)", "builtin-slide", + "builtin-slide" == selectedTransitionAnimation); + } + if (std::find(currentSet->second.capabilities.suppressedTransitionEntries.cbegin(), + currentSet->second.capabilities.suppressedTransitionEntries.cend(), + "builtin-fade") == + currentSet->second.capabilities.suppressedTransitionEntries.cend()) { + themeTransitionAnimations->add("FADE (BUILT-IN)", "builtin-fade", + "builtin-fade" == selectedTransitionAnimation); + } + if (themeTransitionAnimations->getSelectedObjects().size() == 0) + themeTransitionAnimations->selectEntry(0); + } + }; + + themeTransitionAnimationsFunc(Settings::getInstance()->getString("ThemeSet"), + Settings::getInstance()->getString("ThemeTransitionAnimations")); + // Legacy gamelist view style. auto gamelistViewStyle = std::make_shared>( getHelpStyle(), "LEGACY GAMELIST VIEW STYLE", false); @@ -318,21 +413,28 @@ void GuiMenu::openUIOptions() } }); - // Legacy ransition style. - auto transitionStyle = std::make_shared>( - getHelpStyle(), "LEGACY TRANSITION STYLE", false); - std::vector transitions; - transitions.push_back("slide"); - transitions.push_back("fade"); - transitions.push_back("instant"); - for (auto it = transitions.cbegin(); it != transitions.cend(); ++it) - transitionStyle->add(*it, *it, - Settings::getInstance()->getString("TransitionStyle") == *it); - s->addWithLabel("LEGACY TRANSITION STYLE", transitionStyle); - s->addSaveFunc([transitionStyle, s] { - if (transitionStyle->getSelected() != - Settings::getInstance()->getString("TransitionStyle")) { - Settings::getInstance()->setString("TransitionStyle", transitionStyle->getSelected()); + // Legacy transition animations. + auto legacyTransitionAnimations = std::make_shared>( + getHelpStyle(), "LEGACY TRANSITION ANIMS", false); + const std::string& selectedLegacyAnimations { + Settings::getInstance()->getString("LegacyTransitionAnimations")}; + legacyTransitionAnimations->add("INSTANT", "builtin-instant", + selectedLegacyAnimations == "builtin-instant"); + legacyTransitionAnimations->add("SLIDE", "builtin-slide", + selectedLegacyAnimations == "builtin-slide"); + legacyTransitionAnimations->add("FADE", "builtin-fade", + selectedLegacyAnimations == "builtin-fade"); + // If there are no objects returned, then there must be a manually modified entry in the + // configuration file. Simply set the animations to "builtin-instant" in this case. + if (legacyTransitionAnimations->getSelectedObjects().size() == 0) + legacyTransitionAnimations->selectEntry(0); + s->addWithLabel("LEGACY TRANSITION ANIMATIONS", legacyTransitionAnimations); + s->addSaveFunc([legacyTransitionAnimations, s] { + if (legacyTransitionAnimations->getSelected() != + Settings::getInstance()->getString("LegacyTransitionAnimations")) { + Settings::getInstance()->setString("LegacyTransitionAnimations", + legacyTransitionAnimations->getSelected()); + ThemeData::setThemeTransitions(); s->setNeedsSaving(); } }); @@ -729,6 +831,7 @@ void GuiMenu::openUIOptions() themeVariantsFunc(themeName, themeVariant->getSelected()); themeColorSchemesFunc(themeName, themeColorScheme->getSelected()); themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected()); + themeTransitionAnimationsFunc(themeName, themeTransitionAnimations->getSelected()); } int selectableVariants {0}; for (auto& variant : selectedSet->second.capabilities.variants) { @@ -785,12 +888,18 @@ void GuiMenu::openUIOptions() gamelistViewStyle->getParent() ->getChild(gamelistViewStyle->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); - // TEMPORARY - // transitionStyle->setEnabled(false); - // transitionStyle->setOpacity(DISABLED_OPACITY); - // transitionStyle->getParent() - // ->getChild(transitionStyle->getChildIndex() - 1) - // ->setOpacity(DISABLED_OPACITY); + + themeTransitionAnimations->setEnabled(true); + themeTransitionAnimations->setOpacity(1.0f); + themeTransitionAnimations->getParent() + ->getChild(themeTransitionAnimations->getChildIndex() - 1) + ->setOpacity(1.0f); + + legacyTransitionAnimations->setEnabled(false); + legacyTransitionAnimations->setOpacity(DISABLED_OPACITY); + legacyTransitionAnimations->getParent() + ->getChild(legacyTransitionAnimations->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); // Pillarboxes are theme-controlled for newer themes. gamelistVideoPillarbox->setEnabled(false); @@ -813,10 +922,16 @@ void GuiMenu::openUIOptions() ->getChild(gamelistViewStyle->getChildIndex() - 1) ->setOpacity(1.0f); - transitionStyle->setEnabled(true); - transitionStyle->setOpacity(1.0f); - transitionStyle->getParent() - ->getChild(transitionStyle->getChildIndex() - 1) + themeTransitionAnimations->setEnabled(false); + themeTransitionAnimations->setOpacity(DISABLED_OPACITY); + themeTransitionAnimations->getParent() + ->getChild(themeTransitionAnimations->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + + legacyTransitionAnimations->setEnabled(true); + legacyTransitionAnimations->setOpacity(1.0f); + legacyTransitionAnimations->getParent() + ->getChild(legacyTransitionAnimations->getChildIndex() - 1) ->setOpacity(1.0f); gamelistVideoPillarbox->setEnabled(true); diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 8f8c143e2..7f7b795a7 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -227,8 +227,9 @@ void SystemView::onCursorChanged(const CursorState& state) { const int cursor {mPrimary->getCursor()}; const int scrollVelocity {mPrimary->getScrollingVelocity()}; - const std::string& transitionStyle {Settings::getInstance()->getString("TransitionStyle")}; - mFadeTransitions = transitionStyle == "fade"; + const ViewTransitionAnimation transitionAnim {static_cast( + Settings::getInstance()->getInt("TransitionsSystemToSystem"))}; + mFadeTransitions = (transitionAnim == ViewTransitionAnimation::FADE); // Some logic needed to avoid various navigation glitches with GridComponent and // TextListComponent. @@ -278,7 +279,7 @@ void SystemView::onCursorChanged(const CursorState& state) // This is needed to avoid erratic camera movements during extreme navigation input when using // slide transitions. This should very rarely occur during normal application usage. - if (transitionStyle == "slide") { + if (transitionAnim == ViewTransitionAnimation::SLIDE) { bool resetCamOffset {false}; if (scrollVelocity == -1 && mPreviousScrollVelocity == 1) { @@ -372,7 +373,7 @@ void SystemView::onCursorChanged(const CursorState& state) animTime = glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), timeMin, animTime); - if (transitionStyle == "fade") { + if (transitionAnim == ViewTransitionAnimation::FADE) { float startFade {mFadeOpacity}; anim = new LambdaAnimation( [this, startFade, endPos](float t) { @@ -398,7 +399,7 @@ void SystemView::onCursorChanged(const CursorState& state) }, static_cast(animTime * 1.3f)); } - else if (transitionStyle == "slide") { + else if (transitionAnim == ViewTransitionAnimation::SLIDE) { mUpdatedGameCount = false; anim = new LambdaAnimation( [this, startPos, endPos, posMax](float t) { @@ -815,7 +816,8 @@ void SystemView::populate() } } - mFadeTransitions = Settings::getInstance()->getString("TransitionStyle") == "fade"; + mFadeTransitions = (static_cast(Settings::getInstance()->getInt( + "TransitionsSystemToSystem")) == ViewTransitionAnimation::FADE); } void SystemView::updateGameCount() diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index dace0cc9e..91cd0c8f1 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -37,6 +37,7 @@ ViewController::ViewController() noexcept , mCurrentView {nullptr} , mPreviousView {nullptr} , mSkipView {nullptr} + , mLastTransitionAnim {ViewTransitionAnimation::INSTANT} , mGameToLaunch {nullptr} , mCamera {Renderer::getIdentity()} , mSystemViewTransition {false} @@ -220,7 +221,7 @@ void ViewController::goToStart(bool playTransition) // If a specific system is requested, go directly to its game list. auto requestedSystem = Settings::getInstance()->getString("StartupSystem"); - if ("" != requestedSystem && "retropie" != requestedSystem) { + if (requestedSystem != "") { for (auto it = SystemData::sSystemVector.cbegin(); // Line break. it != SystemData::sSystemVector.cend(); ++it) { if ((*it)->getName() == requestedSystem) { @@ -263,7 +264,7 @@ bool ViewController::isCameraMoving() void ViewController::cancelViewTransitions() { - if (Settings::getInstance()->getString("TransitionStyle") == "slide") { + if (mLastTransitionAnim == ViewTransitionAnimation::SLIDE) { if (isCameraMoving()) { mCamera[3].x = -mCurrentView->getPosition().x; mCamera[3].y = -mCurrentView->getPosition().y; @@ -277,7 +278,7 @@ void ViewController::cancelViewTransitions() mSkipView = nullptr; } } - else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { + else if (mLastTransitionAnim == ViewTransitionAnimation::FADE) { if (isAnimationPlaying(0)) { finishAnimation(0); mCancelledTransition = true; @@ -354,8 +355,11 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) // Application startup animation. if (applicationStartup) { + const ViewTransitionAnimation transitionAnim {static_cast( + Settings::getInstance()->getInt("TransitionsStartupToSystem"))}; + mCamera = glm::translate(mCamera, glm::round(-mCurrentView->getPosition())); - if (Settings::getInstance()->getString("TransitionStyle") == "slide") { + if (transitionAnim == ViewTransitionAnimation::SLIDE) { if (getSystemListView()->getPrimaryType() == SystemView::PrimaryType::CAROUSEL) { if (getSystemListView()->getCarouselType() == CarouselComponent::CarouselType::HORIZONTAL || @@ -370,7 +374,7 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) } updateHelpPrompts(); } - else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { + else if (transitionAnim == ViewTransitionAnimation::FADE) { if (getSystemListView()->getPrimaryType() == SystemView::PrimaryType::CAROUSEL) { if (getSystemListView()->getCarouselType() == CarouselComponent::CarouselType::HORIZONTAL || @@ -389,18 +393,21 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) } } + if (applicationStartup) { #if defined(__APPLE__) - // The startup animations are very choppy on macOS as of moving to SDL 2.0.18 so the - // best user experience is to simply disable them on this OS. - if (applicationStartup) - playViewTransition(true); - else if (playTransition) + // The startup animations are very choppy on macOS as of moving to SDL 2.0.18 so the + // best user experience is to simply disable them on this OS. + playViewTransition(ViewTransition::STARTUP_TO_SYSTEM, true); #else - if (playTransition || applicationStartup) + playViewTransition(ViewTransition::STARTUP_TO_SYSTEM); #endif - playViewTransition(); - else - playViewTransition(true); + } + else if (playTransition) { + playViewTransition(ViewTransition::GAMELIST_TO_SYSTEM); + } + else { + playViewTransition(ViewTransition::GAMELIST_TO_SYSTEM, true); + } } void ViewController::goToSystem(SystemData* system, bool animate) @@ -433,21 +440,37 @@ void ViewController::goToGamelist(SystemData* system) bool wrapFirstToLast {false}; bool wrapLastToFirst {false}; bool slideTransitions {false}; + bool fadeTransitions {false}; if (mCurrentView != nullptr) mCurrentView->onTransition(); - if (Settings::getInstance()->getString("TransitionStyle") == "slide") + ViewTransition transitionType; + ViewTransitionAnimation transitionAnim; + + if (mState.viewing == SYSTEM_SELECT) { + transitionType = ViewTransition::SYSTEM_TO_GAMELIST; + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsSystemToGamelist")); + } + else { + transitionType = ViewTransition::GAMELIST_TO_GAMELIST; + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsGamelistToGamelist")); + } + + if (transitionAnim == ViewTransitionAnimation::SLIDE) slideTransitions = true; + if (transitionAnim == ViewTransitionAnimation::FADE) + fadeTransitions = true; + // Restore the X position for the view, if it was previously moved. if (mWrappedViews) restoreViewPosition(); - if (mPreviousView && Settings::getInstance()->getString("TransitionStyle") == "fade" && - isAnimationPlaying(0)) { + if (mPreviousView && fadeTransitions && isAnimationPlaying(0)) mPreviousView->onHide(); - } if (mPreviousView) { mSkipView = mPreviousView; @@ -540,12 +563,17 @@ void ViewController::goToGamelist(SystemData* system) // Application startup animation, if starting in a gamelist rather than in the system view. if (mState.viewing == NOTHING) { + if (mLastTransitionAnim == ViewTransitionAnimation::FADE) + cancelViewTransitions(); + transitionType = ViewTransition::STARTUP_TO_GAMELIST; mCamera = glm::translate(mCamera, glm::round(-mCurrentView->getPosition())); - if (Settings::getInstance()->getString("TransitionStyle") == "slide") { + if (static_cast(Settings::getInstance()->getInt( + "TransitionsStartupToGamelist")) == ViewTransitionAnimation::SLIDE) { mCamera[3].y -= Renderer::getScreenHeight(); updateHelpPrompts(); } - else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { + else if (static_cast(Settings::getInstance()->getInt( + "TransitionsStartupToGamelist")) == ViewTransitionAnimation::FADE) { mCamera[3].y += Renderer::getScreenHeight() * 2.0f; } else { @@ -570,14 +598,14 @@ void ViewController::goToGamelist(SystemData* system) if (mCurrentView) mCurrentView->onShow(); - playViewTransition(); + playViewTransition(transitionType); } -void ViewController::playViewTransition(bool instant) +void ViewController::playViewTransition(ViewTransition transitionType, bool instant) { mCancelledTransition = false; - glm::vec3 target {}; + glm::vec3 target {0.0f, 0.0f, 0.0f}; if (mCurrentView) target = mCurrentView->getPosition(); @@ -586,9 +614,30 @@ void ViewController::playViewTransition(bool instant) if (target == static_cast(-mCamera[3]) && !isAnimationPlaying(0)) return; - std::string transition_style {Settings::getInstance()->getString("TransitionStyle")}; + ViewTransitionAnimation transitionAnim {ViewTransitionAnimation::INSTANT}; - if (instant || transition_style == "instant") { + if (transitionType == ViewTransition::SYSTEM_TO_SYSTEM) + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsSystemToSystem")); + else if (transitionType == ViewTransition::SYSTEM_TO_GAMELIST) + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsSystemToGamelist")); + else if (transitionType == ViewTransition::GAMELIST_TO_GAMELIST) + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsGamelistToGamelist")); + else if (transitionType == ViewTransition::GAMELIST_TO_SYSTEM) + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsGamelistToSystem")); + else if (transitionType == ViewTransition::STARTUP_TO_SYSTEM) + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsStartupToSystem")); + else + transitionAnim = static_cast( + Settings::getInstance()->getInt("TransitionsStartupToGamelist")); + + mLastTransitionAnim = transitionAnim; + + if (instant || transitionAnim == ViewTransitionAnimation::INSTANT) { setAnimation(new LambdaAnimation( [this, target](float /*t*/) { this->mCamera[3].x = -target.x; @@ -600,7 +649,7 @@ void ViewController::playViewTransition(bool instant) 1)); updateHelpPrompts(); } - else if (transition_style == "fade") { + else if (transitionAnim == ViewTransitionAnimation::FADE) { // Stop whatever's currently playing, leaving mFadeOpacity wherever it is. cancelAnimation(0); @@ -641,7 +690,7 @@ void ViewController::playViewTransition(bool instant) advanceAnimation(0, static_cast(mFadeOpacity * FADE_DURATION)); } } - else if (transition_style == "slide") { + else if (transitionAnim == ViewTransitionAnimation::SLIDE) { auto slideCallback = [this]() { if (mSkipView) { mSkipView->onHide(); @@ -1046,7 +1095,7 @@ void ViewController::render(const glm::mat4& parentTrans) void ViewController::preload() { - unsigned int systemCount = static_cast(SystemData::sSystemVector.size()); + unsigned int systemCount {static_cast(SystemData::sSystemVector.size())}; // This reduces the amount of texture pop-in when loading theme extras. if (!SystemData::sSystemVector.empty()) @@ -1064,6 +1113,8 @@ void ViewController::preload() getGamelistView(*it)->preloadGamelist(); } + ThemeData::setThemeTransitions(); + // Load navigation sounds, either from the theme if it supports it, or otherwise from // the bundled fallback sound files. bool themeSoundSupport {false}; @@ -1082,7 +1133,7 @@ void ViewController::reloadGamelistView(GamelistView* view, bool reloadTheme) { for (auto it = mGamelistViews.cbegin(); it != mGamelistViews.cend(); ++it) { if (it->second.get() == view) { - bool isCurrent {(mCurrentView == it->second)}; + bool isCurrent {mCurrentView == it->second}; SystemData* system {it->first}; FileData* cursor {view->getCursor()}; @@ -1147,6 +1198,8 @@ void ViewController::reloadAll() it->first->getIndex()->resetFilters(); } + ThemeData::setThemeTransitions(); + // Rebuild SystemListView. mSystemListView.reset(); getSystemListView(); @@ -1160,7 +1213,7 @@ void ViewController::reloadAll() mCurrentView = getGamelistView(mState.getSystem()); } else if (mState.viewing == SYSTEM_SELECT) { - SystemData* system = mState.getSystem(); + SystemData* system {mState.getSystem()}; mSystemListView->goToSystem(system, false); mCurrentView = mSystemListView; mCamera[3].x = 0.0f; diff --git a/es-app/src/views/ViewController.h b/es-app/src/views/ViewController.h index d72f1d6fd..64859aa6e 100644 --- a/es-app/src/views/ViewController.h +++ b/es-app/src/views/ViewController.h @@ -160,7 +160,7 @@ private: std::string mRomDirectory; GuiMsgBox* mNoGamesMessageBox; - void playViewTransition(bool instant = false); + void playViewTransition(ViewTransition transitionType, bool instant = false); int getSystemId(SystemData* system); // Restore view position if it was moved during wrap around. void restoreViewPosition(); @@ -170,6 +170,7 @@ private: std::shared_ptr mSkipView; std::map> mGamelistViews; std::shared_ptr mSystemListView; + ViewTransitionAnimation mLastTransitionAnim; std::vector mGameEndEventParams; FileData* mGameToLaunch; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 6be172edf..02bcc8d67 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -34,6 +34,21 @@ class InputConfig; class ThemeData; class Window; +enum class ViewTransition { + SYSTEM_TO_SYSTEM, + SYSTEM_TO_GAMELIST, + GAMELIST_TO_GAMELIST, + GAMELIST_TO_SYSTEM, + STARTUP_TO_SYSTEM, + STARTUP_TO_GAMELIST +}; + +enum ViewTransitionAnimation { + INSTANT, + SLIDE, + FADE +}; + enum Alignment { ALIGN_LEFT, ALIGN_CENTER, // Used for both horizontal and vertical alignments. diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index d3f8c1a35..0c18e688d 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -9,6 +9,7 @@ #include "Settings.h" +#include "GuiComponent.h" #include "Log.h" #include "Scripting.h" #include "utils/FileSystemUtil.h" @@ -20,10 +21,8 @@ namespace { - // These values are NOT saved to es_settings.xml since they're not set via - // the in-program settings menu. Most can be set using command-line arguments, - // but some are debug flags that are either hardcoded or set by internal debug - // functions. + // These settings are not saved to es_settings.xml. Most can be set using command-line + // arguments but a couple are debug flags or used for other application-internal purposes. std::vector settingsSkipSaving { // clang-format off // These options can be set using command-line arguments: @@ -38,18 +37,24 @@ namespace "ForceKiosk", // --force-kiosk "ForceKid", // --force-kid - // These options are not shown in the --help text and are intended + // These command-line argument options are not shown in the --help text and are intended // for debugging and testing purposes: "ScreenWidth", // Set via --screensize [width] [height] "ScreenHeight", // set via --screensize [width] [height] "ScreenOffsetX", // Set via --screenoffset [X] [Y] "ScreenOffsetY", // Set via --screenoffset [X] [Y] - // These options are not configurable from the command-line: + // These options are only used internally during the application session: "DebugGrid", "DebugText", "DebugImage", - "ScraperFilter" + "ScraperFilter", + "TransitionsSystemToSystem", + "TransitionsSystemToGamelist", + "TransitionsGamelistToGamelist", + "TransitionsGamelistToSystem", + "TransitionsStartupToSystem", + "TransitionsStartupToGamelist" // clang-format on }; @@ -151,8 +156,9 @@ void Settings::setDefaults() mStringMap["ThemeVariant"] = {"", ""}; mStringMap["ThemeColorScheme"] = {"", ""}; mStringMap["ThemeAspectRatio"] = {"", ""}; + mStringMap["ThemeTransitionAnimations"] = {"automatic", "automatic"}; mStringMap["GamelistViewStyle"] = {"automatic", "automatic"}; - mStringMap["TransitionStyle"] = {"slide", "slide"}; + mStringMap["LegacyTransitionAnimations"] = {"builtin-instant", "builtin-instant"}; mStringMap["QuickSystemSelect"] = {"leftrightshoulders", "leftrightshoulders"}; mStringMap["StartupSystem"] = {"", ""}; mStringMap["DefaultSortOrder"] = {"filename, ascending", "filename, ascending"}; @@ -323,6 +329,19 @@ void Settings::setDefaults() mBoolMap["DebugText"] = {false, false}; mBoolMap["DebugImage"] = {false, false}; mIntMap["ScraperFilter"] = {0, 0}; + + mIntMap["TransitionsSystemToSystem"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; + mIntMap["TransitionsSystemToGamelist"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; + mIntMap["TransitionsGamelistToGamelist"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; + mIntMap["TransitionsGamelistToSystem"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; + mIntMap["TransitionsStartupToSystem"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; + mIntMap["TransitionsStartupToGamelist"] = {ViewTransitionAnimation::INSTANT, + ViewTransitionAnimation::INSTANT}; } void Settings::saveFile() diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 06e3a6b2e..5b990194e 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -40,6 +40,19 @@ std::vector ThemeData::sSupportedMediaTypes { {"fanart"}, {"video"}}; +std::vector ThemeData::sSupportedTransitions { + {"systemToSystem"}, + {"systemToGamelist"}, + {"gamelistToGamelist"}, + {"gamelistToSystem"}, + {"startupToSystem"}, + {"startupToGamelist"}}; + +std::vector ThemeData::sSupportedTransitionAnimations { + {"builtin-instant"}, + {"builtin-slide"}, + {"builtin-fade"}}; + std::vector ThemeData::sLegacySupportedViews { {"all"}, {"system"}, @@ -495,6 +508,7 @@ ThemeData::ThemeData() , mCustomCollection {false} { mCurrentThemeSet = mThemeSets.find(Settings::getInstance()->getString("ThemeSet")); + mVariantDefinedTransitions = ""; } void ThemeData::loadFile(const std::map& sysDataMap, @@ -819,6 +833,95 @@ const std::string ThemeData::getAspectRatioLabel(const std::string& aspectRatio) return "invalid ratio"; } +void ThemeData::setThemeTransitions() +{ + auto setTransitionsFunc = [](int transitionAnim) { + Settings::getInstance()->setInt("TransitionsSystemToSystem", transitionAnim); + Settings::getInstance()->setInt("TransitionsSystemToGamelist", transitionAnim); + Settings::getInstance()->setInt("TransitionsGamelistToGamelist", transitionAnim); + Settings::getInstance()->setInt("TransitionsGamelistToSystem", transitionAnim); + Settings::getInstance()->setInt("TransitionsStartupToSystem", transitionAnim); + Settings::getInstance()->setInt("TransitionsStartupToGamelist", transitionAnim); + }; + + int transitionAnim {ViewTransitionAnimation::INSTANT}; + setTransitionsFunc(transitionAnim); + + if (mCurrentThemeSet->second.capabilities.legacyTheme) { + const std::string& legacyTransitionSetting { + Settings::getInstance()->getString("LegacyTransitionAnimations")}; + if (legacyTransitionSetting == "builtin-slide") + transitionAnim = static_cast(ViewTransitionAnimation::SLIDE); + else if (legacyTransitionSetting == "builtin-fade") + transitionAnim = static_cast(ViewTransitionAnimation::FADE); + setTransitionsFunc(transitionAnim); + } + else { + const std::string& transitionSetting { + Settings::getInstance()->getString("ThemeTransitionAnimations")}; + std::string profile; + size_t profileEntry {0}; + + if (transitionSetting == "automatic") { + if (mVariantDefinedTransitions != "") + profile = mVariantDefinedTransitions; + else if (!mCurrentThemeSet->second.capabilities.transitions.empty()) + profile = mCurrentThemeSet->second.capabilities.transitions.front().name; + } + else { + profile = transitionSetting; + } + + auto it = std::find_if( + mCurrentThemeSet->second.capabilities.transitions.cbegin(), + mCurrentThemeSet->second.capabilities.transitions.cend(), + [&profile](const ThemeTransitions transitions) { return transitions.name == profile; }); + if (it != mCurrentThemeSet->second.capabilities.transitions.cend()) + profileEntry = static_cast( + std::distance(mCurrentThemeSet->second.capabilities.transitions.cbegin(), it) + 1); + + if (profileEntry != 0 && + mCurrentThemeSet->second.capabilities.transitions.size() > profileEntry - 1) { + auto transitionMap = + mCurrentThemeSet->second.capabilities.transitions[profileEntry - 1].animations; + if (transitionMap.find(ViewTransition::SYSTEM_TO_SYSTEM) != transitionMap.end()) + Settings::getInstance()->setInt("TransitionsSystemToSystem", + transitionMap[ViewTransition::SYSTEM_TO_SYSTEM]); + if (transitionMap.find(ViewTransition::SYSTEM_TO_GAMELIST) != transitionMap.end()) + Settings::getInstance()->setInt("TransitionsSystemToGamelist", + transitionMap[ViewTransition::SYSTEM_TO_GAMELIST]); + if (transitionMap.find(ViewTransition::GAMELIST_TO_GAMELIST) != transitionMap.end()) + Settings::getInstance()->setInt( + "TransitionsGamelistToGamelist", + transitionMap[ViewTransition::GAMELIST_TO_GAMELIST]); + if (transitionMap.find(ViewTransition::GAMELIST_TO_SYSTEM) != transitionMap.end()) + Settings::getInstance()->setInt("TransitionsGamelistToSystem", + transitionMap[ViewTransition::GAMELIST_TO_SYSTEM]); + if (transitionMap.find(ViewTransition::STARTUP_TO_SYSTEM) != transitionMap.end()) + Settings::getInstance()->setInt("TransitionsStartupToSystem", + transitionMap[ViewTransition::STARTUP_TO_SYSTEM]); + if (transitionMap.find(ViewTransition::STARTUP_TO_GAMELIST) != transitionMap.end()) + Settings::getInstance()->setInt("TransitionsStartupToGamelist", + transitionMap[ViewTransition::STARTUP_TO_GAMELIST]); + } + else if (transitionSetting == "builtin-slide" || transitionSetting == "builtin-fade") { + if (std::find( + mCurrentThemeSet->second.capabilities.suppressedTransitionEntries.cbegin(), + mCurrentThemeSet->second.capabilities.suppressedTransitionEntries.cend(), + transitionSetting) == + mCurrentThemeSet->second.capabilities.suppressedTransitionEntries.cend()) { + if (transitionSetting == "builtin-slide") { + transitionAnim = static_cast(ViewTransitionAnimation::SLIDE); + } + else if (transitionSetting == "builtin-fade") { + transitionAnim = static_cast(ViewTransitionAnimation::FADE); + } + setTransitionsFunc(transitionAnim); + } + } + } +} + const std::map>> ThemeData::getCurrentThemeSetSelectedVariantOverrides() { @@ -1128,6 +1231,140 @@ ThemeData::ThemeCapability ThemeData::parseThemeCapabilities(const std::string& capabilities.colorSchemes.emplace_back(readColorScheme); } } + + for (pugi::xml_node transitions {themeCapabilities.child("transitions")}; transitions; + transitions = transitions.next_sibling("transitions")) { + std::map readTransitions; + std::string name {transitions.attribute("name").as_string()}; + std::string label; + bool selectable {true}; + + if (name.empty()) { + LOG(LogWarning) + << "Found tag without name attribute, ignoring entry in \"" + << capFile << "\""; + name.clear(); + } + else { + if (std::find(sSupportedTransitionAnimations.cbegin(), + sSupportedTransitionAnimations.cend(), + name) != sSupportedTransitionAnimations.cend()) { + LOG(LogWarning) + << "Found tag using reserved name attribute value \"" << name + << "\", ignoring entry in \"" << capFile << "\""; + name.clear(); + } + else { + for (auto& transitionEntry : capabilities.transitions) { + if (transitionEntry.name == name) { + LOG(LogWarning) + << "Found tag with previously used name attribute " + "value \"" + << name << "\", ignoring entry in \"" << capFile << "\""; + name.clear(); + } + } + } + } + + if (name == "") + continue; + + const pugi::xml_node& labelTag {transitions.child("label")}; + if (labelTag != nullptr) + label = labelTag.text().as_string(); + + const pugi::xml_node& selectableTag {transitions.child("selectable")}; + if (selectableTag != nullptr) { + const std::string& value {selectableTag.text().as_string()}; + if (value.front() == '0' || value.front() == 'f' || value.front() == 'F' || + value.front() == 'n' || value.front() == 'N') + selectable = false; + } + + for (auto& currTransition : sSupportedTransitions) { + const pugi::xml_node& transitionTag {transitions.child(currTransition.c_str())}; + if (transitionTag != nullptr) { + const std::string& transitionValue {transitionTag.text().as_string()}; + if (transitionValue.empty()) { + LOG(LogWarning) << "Found <" << currTransition + << "> transition tag without any value, " + "ignoring entry in \"" + << capFile << "\""; + } + else if (std::find(sSupportedTransitionAnimations.cbegin(), + sSupportedTransitionAnimations.cend(), + currTransition) != sSupportedTransitionAnimations.cend()) { + LOG(LogWarning) + << "Invalid <" << currTransition << "> transition tag value \"" + << transitionValue << "\", ignoring entry in \"" << capFile << "\""; + } + else { + ViewTransitionAnimation transitionAnim {ViewTransitionAnimation::INSTANT}; + if (transitionValue == "slide") + transitionAnim = ViewTransitionAnimation::SLIDE; + else if (transitionValue == "fade") + transitionAnim = ViewTransitionAnimation::FADE; + + if (currTransition == "systemToSystem") + readTransitions[ViewTransition::SYSTEM_TO_SYSTEM] = transitionAnim; + else if (currTransition == "systemToGamelist") + readTransitions[ViewTransition::SYSTEM_TO_GAMELIST] = transitionAnim; + else if (currTransition == "gamelistToGamelist") + readTransitions[ViewTransition::GAMELIST_TO_GAMELIST] = transitionAnim; + else if (currTransition == "gamelistToSystem") + readTransitions[ViewTransition::GAMELIST_TO_SYSTEM] = transitionAnim; + else if (currTransition == "startupToSystem") + readTransitions[ViewTransition::STARTUP_TO_SYSTEM] = transitionAnim; + else if (currTransition == "startupToGamelist") + readTransitions[ViewTransition::STARTUP_TO_GAMELIST] = transitionAnim; + } + } + } + + if (!readTransitions.empty()) { + ThemeTransitions transition; + transition.name = name; + transition.label = label; + transition.selectable = selectable; + transition.animations = std::move(readTransitions); + capabilities.transitions.emplace_back(std::move(transition)); + } + } + + for (pugi::xml_node suppressTransitionEntries { + themeCapabilities.child("suppressTransitionEntries")}; + suppressTransitionEntries; + suppressTransitionEntries = + suppressTransitionEntries.next_sibling("suppressTransitionEntries")) { + std::vector readSuppressEntries; + + for (pugi::xml_node entries {suppressTransitionEntries.child("entry")}; entries; + entries = entries.next_sibling("entry")) { + const std::string& entryValue {entries.text().as_string()}; + + if (std::find(sSupportedTransitionAnimations.cbegin(), + sSupportedTransitionAnimations.cend(), + entryValue) != sSupportedTransitionAnimations.cend()) { + capabilities.suppressedTransitionEntries.emplace_back(entryValue); + } + else { + LOG(LogWarning) + << "Found suppressTransitionEntries tag with invalid value \"" + << entryValue << "\", ignoring entry in \"" << capFile << "\""; + } + } + + // Sort and remove any duplicates. + if (capabilities.suppressedTransitionEntries.size() > 1) { + std::sort(capabilities.suppressedTransitionEntries.begin(), + capabilities.suppressedTransitionEntries.end()); + auto last = std::unique(capabilities.suppressedTransitionEntries.begin(), + capabilities.suppressedTransitionEntries.end()); + capabilities.suppressedTransitionEntries.erase( + last, capabilities.suppressedTransitionEntries.end()); + } + } } else { LOG(LogDebug) << "No capabilities.xml file found, flagging as legacy theme set"; @@ -1311,6 +1548,21 @@ void ThemeData::parseVariants(const pugi::xml_node& root) mOverrideVariant}; if (variant == viewKey || viewKey == "all") { + const pugi::xml_node& transitions {node.child("transitions")}; + if (transitions != nullptr) { + const std::string& transitionsValue {transitions.text().as_string()}; + if (std::find_if(mCurrentThemeSet->second.capabilities.transitions.cbegin(), + mCurrentThemeSet->second.capabilities.transitions.cend(), + [&transitionsValue](const ThemeTransitions transitions) { + return transitions.name == transitionsValue; + }) == + mCurrentThemeSet->second.capabilities.transitions.cend()) { + throw error << ": value \"" << transitionsValue + << "\" is not matching any defined transitions"; + } + mVariantDefinedTransitions = transitionsValue; + } + parseVariables(node); parseColorSchemes(node); parseIncludes(node); diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 2d718bb26..cb0982048 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -11,6 +11,7 @@ #ifndef ES_CORE_THEME_DATA_H #define ES_CORE_THEME_DATA_H +#include "GuiComponent.h" #include "utils/FileSystemUtil.h" #include "utils/MathUtil.h" #include "utils/StringUtil.h" @@ -198,10 +199,24 @@ public: std::string label; }; + struct ThemeTransitions { + std::string name; + std::string label; + bool selectable; + std::map animations; + + ThemeTransitions() + : selectable {true} + { + } + }; + struct ThemeCapability { std::vector variants; std::vector colorSchemes; std::vector aspectRatios; + std::vector transitions; + std::vector suppressedTransitionEntries; bool legacyTheme; }; @@ -245,6 +260,7 @@ public: const static std::string getThemeFromCurrentSet(const std::string& system); const static std::string getAspectRatioLabel(const std::string& aspectRatio); const static std::string getCurrentThemeSetName() { return mCurrentThemeSet->first; } + static void setThemeTransitions(); const bool isLegacyTheme() { return mLegacyTheme; } const std::map>> @@ -291,6 +307,8 @@ private: static std::vector sSupportedViews; static std::vector sSupportedMediaTypes; + static std::vector sSupportedTransitions; + static std::vector sSupportedTransitionAnimations; static std::vector sLegacySupportedViews; static std::vector sLegacySupportedFeatures; static std::vector sLegacyProperties; @@ -302,6 +320,7 @@ private: static inline std::map mThemeSets; static inline std::map::iterator mCurrentThemeSet {}; + static inline std::string mVariantDefinedTransitions; std::map mViews; std::deque mPaths;