Added support for theme-controlled transition animations.

This commit is contained in:
Leon Styhre 2023-01-08 17:00:36 +01:00
parent 1f0bf21675
commit 71b4fc947a
8 changed files with 546 additions and 70 deletions

View file

@ -294,6 +294,101 @@ void GuiMenu::openUIOptions()
themeAspectRatiosFunc(Settings::getInstance()->getString("ThemeSet"),
Settings::getInstance()->getString("ThemeAspectRatio"));
// Theme transition animations.
auto themeTransitionAnimations = std::make_shared<OptionListComponent<std::string>>(
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<std::string, ThemeData::ThemeSet, ThemeData::StringComparator>::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<OptionListComponent<std::string>>(
getHelpStyle(), "LEGACY GAMELIST VIEW STYLE", false);
@ -318,21 +413,28 @@ void GuiMenu::openUIOptions()
}
});
// Legacy ransition style.
auto transitionStyle = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "LEGACY TRANSITION STYLE", false);
std::vector<std::string> 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<OptionListComponent<std::string>>(
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);

View file

@ -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<ViewTransitionAnimation>(
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<int>(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<ViewTransitionAnimation>(Settings::getInstance()->getInt(
"TransitionsSystemToSystem")) == ViewTransitionAnimation::FADE);
}
void SystemView::updateGameCount()

View file

@ -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<ViewTransitionAnimation>(
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<SystemData*>::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<SystemData*>::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<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsSystemToGamelist"));
}
else {
transitionType = ViewTransition::GAMELIST_TO_GAMELIST;
transitionAnim = static_cast<ViewTransitionAnimation>(
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<ViewTransitionAnimation>(Settings::getInstance()->getInt(
"TransitionsStartupToGamelist")) == ViewTransitionAnimation::SLIDE) {
mCamera[3].y -= Renderer::getScreenHeight();
updateHelpPrompts();
}
else if (Settings::getInstance()->getString("TransitionStyle") == "fade") {
else if (static_cast<ViewTransitionAnimation>(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<glm::vec3>(-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<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsSystemToSystem"));
else if (transitionType == ViewTransition::SYSTEM_TO_GAMELIST)
transitionAnim = static_cast<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsSystemToGamelist"));
else if (transitionType == ViewTransition::GAMELIST_TO_GAMELIST)
transitionAnim = static_cast<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsGamelistToGamelist"));
else if (transitionType == ViewTransition::GAMELIST_TO_SYSTEM)
transitionAnim = static_cast<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsGamelistToSystem"));
else if (transitionType == ViewTransition::STARTUP_TO_SYSTEM)
transitionAnim = static_cast<ViewTransitionAnimation>(
Settings::getInstance()->getInt("TransitionsStartupToSystem"));
else
transitionAnim = static_cast<ViewTransitionAnimation>(
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<int>(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<int>(SystemData::sSystemVector.size());
unsigned int systemCount {static_cast<unsigned int>(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;

View file

@ -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<GuiComponent> mSkipView;
std::map<SystemData*, std::shared_ptr<GamelistView>> mGamelistViews;
std::shared_ptr<SystemView> mSystemListView;
ViewTransitionAnimation mLastTransitionAnim;
std::vector<std::string> mGameEndEventParams;
FileData* mGameToLaunch;

View file

@ -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.

View file

@ -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<std::string> 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()

View file

@ -40,6 +40,19 @@ std::vector<std::string> ThemeData::sSupportedMediaTypes {
{"fanart"},
{"video"}};
std::vector<std::string> ThemeData::sSupportedTransitions {
{"systemToSystem"},
{"systemToGamelist"},
{"gamelistToGamelist"},
{"gamelistToSystem"},
{"startupToSystem"},
{"startupToGamelist"}};
std::vector<std::string> ThemeData::sSupportedTransitionAnimations {
{"builtin-instant"},
{"builtin-slide"},
{"builtin-fade"}};
std::vector<std::string> 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<std::string, std::string>& 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<int>(ViewTransitionAnimation::SLIDE);
else if (legacyTransitionSetting == "builtin-fade")
transitionAnim = static_cast<int>(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<size_t>(
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<int>(ViewTransitionAnimation::SLIDE);
}
else if (transitionSetting == "builtin-fade") {
transitionAnim = static_cast<int>(ViewTransitionAnimation::FADE);
}
setTransitionsFunc(transitionAnim);
}
}
}
}
const std::map<ThemeTriggers::TriggerType, std::pair<std::string, std::vector<std::string>>>
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<ViewTransition, ViewTransitionAnimation> readTransitions;
std::string name {transitions.attribute("name").as_string()};
std::string label;
bool selectable {true};
if (name.empty()) {
LOG(LogWarning)
<< "Found <transitions> tag without name attribute, ignoring entry in \""
<< capFile << "\"";
name.clear();
}
else {
if (std::find(sSupportedTransitionAnimations.cbegin(),
sSupportedTransitionAnimations.cend(),
name) != sSupportedTransitionAnimations.cend()) {
LOG(LogWarning)
<< "Found <transitions> 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 <transitions> 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<std::string> 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 <entry> 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 << ": <transitions> value \"" << transitionsValue
<< "\" is not matching any defined transitions";
}
mVariantDefinedTransitions = transitionsValue;
}
parseVariables(node);
parseColorSchemes(node);
parseIncludes(node);

View file

@ -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<ViewTransition, ViewTransitionAnimation> animations;
ThemeTransitions()
: selectable {true}
{
}
};
struct ThemeCapability {
std::vector<ThemeVariant> variants;
std::vector<ThemeColorScheme> colorSchemes;
std::vector<std::string> aspectRatios;
std::vector<ThemeTransitions> transitions;
std::vector<std::string> 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<ThemeTriggers::TriggerType, std::pair<std::string, std::vector<std::string>>>
@ -291,6 +307,8 @@ private:
static std::vector<std::string> sSupportedViews;
static std::vector<std::string> sSupportedMediaTypes;
static std::vector<std::string> sSupportedTransitions;
static std::vector<std::string> sSupportedTransitionAnimations;
static std::vector<std::string> sLegacySupportedViews;
static std::vector<std::string> sLegacySupportedFeatures;
static std::vector<std::string> sLegacyProperties;
@ -302,6 +320,7 @@ private:
static inline std::map<std::string, ThemeSet, StringComparator> mThemeSets;
static inline std::map<std::string, ThemeSet, StringComparator>::iterator mCurrentThemeSet {};
static inline std::string mVariantDefinedTransitions;
std::map<std::string, ThemeView> mViews;
std::deque<std::string> mPaths;