diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp index 6258144bb..949afb0d5 100644 --- a/es-app/src/views/GamelistBase.cpp +++ b/es-app/src/views/GamelistBase.cpp @@ -543,18 +543,21 @@ void GamelistBase::populateList(const std::vector& files, FileData* f mFirstGameEntry = nullptr; bool favoriteStar {true}; bool isEditing {false}; + bool customCollection {false}; std::string editingCollection; std::string inCollectionPrefix; + LetterCase letterCase {LetterCase::NONE}; if (CollectionSystemsManager::getInstance()->isEditing()) { editingCollection = CollectionSystemsManager::getInstance()->getEditingCollection(); isEditing = true; } - // Read the settings that control whether a unicode star character should be added - // as a prefix to the game name. if (files.size() > 0) { - if (files.front()->getSystem()->isCustomCollection()) + customCollection = files.front()->getSystem()->isCustomCollection(); + // Read the settings that control whether a unicode star character should be added + // as a prefix to the game name. + if (customCollection) favoriteStar = Settings::getInstance()->getBool("FavStarCustom"); else favoriteStar = Settings::getInstance()->getBool("FavoritesStar"); @@ -575,13 +578,21 @@ void GamelistBase::populateList(const std::vector& files, FileData* f if (!ResourceManager::getInstance().fileExists(carouselDefaultItem)) carouselDefaultItem = ""; } - if (files.size() > 0) { for (auto it = files.cbegin(); it != files.cend(); ++it) { if (!mFirstGameEntry && (*it)->getType() == GAME) mFirstGameEntry = (*it); + if (customCollection && (*it)->getType() == FOLDER) { + letterCase = mPrimary->getLetterCaseGroupedCollections(); + if (letterCase == LetterCase::NONE) + letterCase = mPrimary->getLetterCase(); + } + else { + letterCase = mPrimary->getLetterCase(); + } + if (mCarousel != nullptr) { assert(carouselItemType != ""); @@ -589,6 +600,13 @@ void GamelistBase::populateList(const std::vector& files, FileData* f carouselEntry.name = (*it)->getName(); carouselEntry.object = *it; + if (letterCase == LetterCase::UPPERCASE) + carouselEntry.name = Utils::String::toUpper(carouselEntry.name); + else if (letterCase == LetterCase::LOWERCASE) + carouselEntry.name = Utils::String::toLower(carouselEntry.name); + else if (letterCase == LetterCase::CAPITALIZED) + carouselEntry.name = Utils::String::toCapitalized(carouselEntry.name); + if (carouselDefaultItem != "") carouselEntry.data.defaultItemPath = carouselDefaultItem; @@ -646,6 +664,14 @@ void GamelistBase::populateList(const std::vector& files, FileData* f name = inCollectionPrefix + (*it)->getName(); } } + + if (letterCase == LetterCase::UPPERCASE) + name = Utils::String::toUpper(name); + else if (letterCase == LetterCase::LOWERCASE) + name = Utils::String::toLower(name); + else if (letterCase == LetterCase::CAPITALIZED) + name = Utils::String::toCapitalized(name); + color = (*it)->getType() == FOLDER; textListEntry.name = name; textListEntry.object = *it; diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 11a5131ec..04fcb77b2 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -637,17 +637,43 @@ void SystemView::populate() }); } + auto letterCaseFunc = [&it, this](std::string& name) { + LetterCase letterCase {LetterCase::NONE}; + if (it->isCollection()) { + letterCase = mPrimary->getLetterCaseCollections(); + if (letterCase == LetterCase::NONE) + letterCase = mPrimary->getLetterCase(); + } + else { + letterCase = mPrimary->getLetterCase(); + } + + if (letterCase == LetterCase::UPPERCASE) + name = Utils::String::toUpper(name); + else if (letterCase == LetterCase::LOWERCASE) + name = Utils::String::toLower(name); + else if (letterCase == LetterCase::CAPITALIZED) + name = Utils::String::toCapitalized(name); + }; + if (mCarousel != nullptr) { CarouselComponent::Entry entry; - entry.name = it->getName(); + // Keep showing only the short name for legacy themes to maintain maximum + // backward compatibility. This also applies to unreadable theme sets. + if (mLegacyMode) + entry.name = it->getName(); + else + entry.name = it->getFullName(); + letterCaseFunc(entry.name); entry.object = it; entry.data.itemPath = itemPath; entry.data.defaultItemPath = defaultItemPath; mCarousel->addEntry(entry, theme); } - if (mTextList != nullptr) { + else if (mTextList != nullptr) { TextListComponent::Entry entry; entry.name = it->getFullName(); + letterCaseFunc(entry.name); entry.object = it; entry.data.colorId = 0; mTextList->addEntry(entry); diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index d9f354317..827bb57bc 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -42,6 +42,13 @@ enum Alignment { ALIGN_BOTTOM }; +enum class LetterCase { + UPPERCASE, + LOWERCASE, + CAPITALIZED, + NONE +}; + class GuiComponent { public: diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 0ec93a40d..0fc9f493f 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -316,6 +316,8 @@ std::map> {"fontPath", PATH}, {"fontSize", FLOAT}, {"letterCase", STRING}, + {"letterCaseCollections", STRING}, + {"letterCaseGroupedCollections", STRING}, {"lineSpacing", FLOAT}, {"fadeAbovePrimary", BOOLEAN}, {"zIndex", FLOAT}, @@ -341,6 +343,8 @@ std::map> {"alignment", STRING}, // For backward compatibility with legacy themes. {"horizontalMargin", FLOAT}, {"letterCase", STRING}, + {"letterCaseCollections", STRING}, + {"letterCaseGroupedCollections", STRING}, {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, {"indicators", STRING}, diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index 6af2068c0..6a47e8023 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -59,6 +59,12 @@ public: const std::string& getDefaultItem() { return mDefaultItem; } void setDefaultItem(std::string defaultItem) { mDefaultItem = defaultItem; } bool isScrolling() const override { return List::isScrolling(); } + const LetterCase getLetterCase() const override { return mLetterCase; } + const LetterCase getLetterCaseCollections() const override { return mLetterCaseCollections; } + const LetterCase getLetterCaseGroupedCollections() const override + { + return mLetterCaseGroupedCollections; + } void setCursorChangedCallback(const std::function& func) override { @@ -116,9 +122,6 @@ private: bool mPositiveDirection; bool mTriggerJump; bool mGamelistView; - bool mUppercase; - bool mLowercase; - bool mCapitalize; CarouselType mType; std::string mItemType; @@ -127,7 +130,6 @@ private: std::shared_ptr mFont; unsigned int mTextColor; unsigned int mTextBackgroundColor; - std::string mText; float mLineSpacing; Alignment mItemHorizontalAlignment; Alignment mItemVerticalAlignment; @@ -141,6 +143,9 @@ private: bool mInstantItemTransitions; bool mItemAxisHorizontal; bool mFadeAbovePrimary; + LetterCase mLetterCase; + LetterCase mLetterCaseCollections; + LetterCase mLetterCaseGroupedCollections; float mItemScale; float mItemRotation; glm::vec2 mItemRotationOrigin; @@ -167,9 +172,6 @@ CarouselComponent::CarouselComponent() , mPositiveDirection {false} , mTriggerJump {false} , mGamelistView {std::is_same_v ? true : false} - , mUppercase {false} - , mLowercase {false} - , mCapitalize {false} , mType {CarouselType::HORIZONTAL} , mLegacyMode {false} , mFont {Font::get(FONT_SIZE_LARGE)} @@ -189,6 +191,9 @@ CarouselComponent::CarouselComponent() , mInstantItemTransitions {false} , mItemAxisHorizontal {false} , mFadeAbovePrimary {false} + , mLetterCase {LetterCase::NONE} + , mLetterCaseCollections {LetterCase::NONE} + , mLetterCaseGroupedCollections {LetterCase::NONE} , mItemScale {1.2f} , mItemRotation {7.5f} , mItemRotationOrigin {-3.0f, 0.5f} @@ -263,20 +268,9 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr( - nameEntry, mFont, 0x000000FF, mItemHorizontalAlignment, mItemVerticalAlignment, + entry.name, mFont, 0x000000FF, mItemHorizontalAlignment, mItemVerticalAlignment, glm::vec3 {0.0f, 0.0f, 0.0f}, glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f)), 0x00000000); if (legacyMode) { @@ -287,10 +281,8 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptrsetLineSpacing(mLineSpacing); - if (!mGamelistView) { - if (mText != "") - text->setValue(mText); - } + if (!mGamelistView) + text->setValue(entry.name); text->setColor(mTextColor); text->setBackgroundColor(mTextBackgroundColor); text->setRenderBackground(true); @@ -317,8 +309,6 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptrsetPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); List::add(entry); - - mText = ""; } template @@ -934,10 +924,6 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, mCarouselColor = 0xFFFFFFD8; mCarouselColorEnd = 0xFFFFFFD8; mZIndex = mDefaultZIndex; - mText = ""; - mUppercase = false; - mLowercase = false; - mCapitalize = false; if (!elem) return; @@ -1229,36 +1215,60 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("lineSpacing")) mLineSpacing = glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f); - std::string letterCase; - bool hasText {!mGamelistView && elem->has("text")}; - if (elem->has("letterCase")) { - letterCase = elem->get("letterCase"); + const std::string letterCase {elem->get("letterCase")}; if (letterCase == "uppercase") { - mUppercase = true; - if (hasText) - mText = Utils::String::toUpper(elem->get("text")); + mLetterCase = LetterCase::UPPERCASE; } else if (letterCase == "lowercase") { - mLowercase = true; - if (hasText) - mText = Utils::String::toLower(elem->get("text")); + mLetterCase = LetterCase::LOWERCASE; } else if (letterCase == "capitalize") { - mCapitalize = true; - if (hasText) - mText = Utils::String::toCapitalized(elem->get("text")); + mLetterCase = LetterCase::CAPITALIZED; } - else if (hasText && letterCase == "none") { - mText = elem->get("text"); - } - else { + else if (letterCase != "none") { LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " "\"letterCase\" for element \"" << element.substr(9) << "\" defined as \"" << letterCase << "\""; - if (hasText) - mText = elem->get("text"); + } + } + + if (elem->has("letterCaseCollections")) { + const std::string letterCase {elem->get("letterCaseCollections")}; + + if (letterCase == "uppercase") { + mLetterCaseCollections = LetterCase::UPPERCASE; + } + else if (letterCase == "lowercase") { + mLetterCaseCollections = LetterCase::LOWERCASE; + } + else if (letterCase == "capitalize") { + mLetterCaseCollections = LetterCase::CAPITALIZED; + } + else { + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + "\"letterCaseCollections\" for element \"" + << element.substr(9) << "\" defined as \"" << letterCase << "\""; + } + } + + if (elem->has("letterCaseGroupedCollections")) { + const std::string letterCase {elem->get("letterCaseGroupedCollections")}; + + if (letterCase == "uppercase") { + mLetterCaseGroupedCollections = LetterCase::UPPERCASE; + } + else if (letterCase == "lowercase") { + mLetterCaseGroupedCollections = LetterCase::LOWERCASE; + } + else if (letterCase == "capitalize") { + mLetterCaseGroupedCollections = LetterCase::CAPITALIZED; + } + else { + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + "\"letterCaseGroupedCollections\" for element \"" + << element.substr(9) << "\" defined as \"" << letterCase << "\""; } } diff --git a/es-core/src/components/primary/PrimaryComponent.h b/es-core/src/components/primary/PrimaryComponent.h index 1a399785d..9df90c1dd 100644 --- a/es-core/src/components/primary/PrimaryComponent.h +++ b/es-core/src/components/primary/PrimaryComponent.h @@ -43,6 +43,9 @@ public: virtual int getCursor() = 0; virtual const size_t getNumEntries() = 0; virtual const bool getFadeAbovePrimary() const = 0; + virtual const LetterCase getLetterCase() const = 0; + virtual const LetterCase getLetterCaseCollections() const = 0; + virtual const LetterCase getLetterCaseGroupedCollections() const = 0; // Functions used by some primary components. virtual void onDemandTextureLoad() {} diff --git a/es-core/src/components/primary/TextListComponent.h b/es-core/src/components/primary/TextListComponent.h index f1013897d..2bc074751 100644 --- a/es-core/src/components/primary/TextListComponent.h +++ b/es-core/src/components/primary/TextListComponent.h @@ -68,45 +68,6 @@ public: it->data.textCache.reset(); } - void setUppercase(bool uppercase) - { - mUppercase = uppercase; - - if (uppercase) { - mLowercase = false; - mCapitalize = false; - } - - for (auto it = mEntries.begin(); it != mEntries.end(); ++it) - it->data.textCache.reset(); - } - - void setLowercase(bool lowercase) - { - mLowercase = lowercase; - - if (lowercase) { - mUppercase = false; - mCapitalize = false; - } - - for (auto it = mEntries.begin(); it != mEntries.end(); ++it) - it->data.textCache.reset(); - } - - void setCapitalize(bool capitalize) - { - mCapitalize = capitalize; - - if (capitalize) { - mUppercase = false; - mLowercase = false; - } - - for (auto it = mEntries.begin(); it != mEntries.end(); ++it) - it->data.textCache.reset(); - } - void setSelectorHeight(float selectorScale) { mSelectorHeight = selectorScale; } void setSelectorOffsetY(float selectorOffsetY) { mSelectorOffsetY = selectorOffsetY; } void setSelectorColor(unsigned int color) { mSelectorColor = color; } @@ -120,6 +81,12 @@ public: void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; } const std::string& getIndicators() const { return mIndicators; } const std::string& getCollectionIndicators() const { return mCollectionIndicators; } + const LetterCase getLetterCase() const override { return mLetterCase; } + const LetterCase getLetterCaseCollections() const override { return mLetterCaseCollections; } + const LetterCase getLetterCaseGroupedCollections() const override + { + return mLetterCaseGroupedCollections; + } protected: void onShow() override { mLoopTime = 0; } @@ -169,9 +136,9 @@ private: std::string mCollectionIndicators; bool mLegacyMode; bool mFadeAbovePrimary; - bool mUppercase; - bool mLowercase; - bool mCapitalize; + LetterCase mLetterCase; + LetterCase mLetterCaseCollections; + LetterCase mLetterCaseGroupedCollections; float mLineSpacing; float mSelectorHeight; float mSelectorOffsetY; @@ -202,9 +169,9 @@ TextListComponent::TextListComponent() , mCollectionIndicators {"symbols"} , mLegacyMode {false} , mFadeAbovePrimary {false} - , mUppercase {false} - , mLowercase {false} - , mCapitalize {false} + , mLetterCase {LetterCase::NONE} + , mLetterCaseCollections {LetterCase::NONE} + , mLetterCaseGroupedCollections {LetterCase::NONE} , mLineSpacing {1.5f} , mSelectorHeight {mFont->getSize() * 1.5f} , mSelectorOffsetY {0.0f} @@ -435,18 +402,8 @@ template void TextListComponent::render(const glm::mat4& parentT color = mColors[entry.data.colorId]; if (!entry.data.textCache) { - if (mUppercase) - entry.data.textCache = std::unique_ptr( - font->buildTextCache(Utils::String::toUpper(entry.name), 0, 0, 0x000000FF)); - else if (mLowercase) - entry.data.textCache = std::unique_ptr( - font->buildTextCache(Utils::String::toLower(entry.name), 0, 0, 0x000000FF)); - else if (mCapitalize) - entry.data.textCache = std::unique_ptr(font->buildTextCache( - Utils::String::toCapitalized(entry.name), 0, 0, 0x000000FF)); - else - entry.data.textCache = - std::unique_ptr(font->buildTextCache(entry.name, 0, 0, 0x000000FF)); + entry.data.textCache = + std::unique_ptr(font->buildTextCache(entry.name, 0, 0, 0x000000FF)); } if constexpr (std::is_same_v) { @@ -604,26 +561,64 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, } if (properties & LETTER_CASE && elem->has("letterCase")) { - std::string letterCase {elem->get("letterCase")}; + const std::string letterCase {elem->get("letterCase")}; if (letterCase == "uppercase") { - setUppercase(true); + mLetterCase = LetterCase::UPPERCASE; } else if (letterCase == "lowercase") { - setLowercase(true); + mLetterCase = LetterCase::LOWERCASE; } else if (letterCase == "capitalize") { - setCapitalize(true); + mLetterCase = LetterCase::CAPITALIZED; } else if (letterCase != "none") { LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " - " defined as \"" - << letterCase << "\""; + "\"letterCase\" for element \"" + << element.substr(9) << "\" defined as \"" << letterCase << "\""; + } + } + + if (properties & LETTER_CASE && elem->has("letterCaseCollections")) { + const std::string letterCase {elem->get("letterCaseCollections")}; + if (letterCase == "uppercase") { + mLetterCaseCollections = LetterCase::UPPERCASE; + } + else if (letterCase == "lowercase") { + mLetterCaseCollections = LetterCase::LOWERCASE; + } + else if (letterCase == "capitalize") { + mLetterCaseCollections = LetterCase::CAPITALIZED; + } + else { + LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " + "\"letterCaseCollections\" for element \"" + << element.substr(9) << "\" defined as \"" << letterCase << "\""; + } + } + + if (properties & LETTER_CASE && elem->has("letterCaseGroupedCollections")) { + const std::string letterCase {elem->get("letterCaseGroupedCollections")}; + if (letterCase == "uppercase") { + mLetterCaseGroupedCollections = LetterCase::UPPERCASE; + } + else if (letterCase == "lowercase") { + mLetterCaseGroupedCollections = LetterCase::LOWERCASE; + } + else if (letterCase == "capitalize") { + mLetterCaseGroupedCollections = LetterCase::CAPITALIZED; + } + else { + LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " + "\"letterCaseGroupedCollections\" for element \"" + << element.substr(9) << "\" defined as \"" << letterCase << "\""; } } // Legacy themes only. - if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) - setUppercase(elem->get("forceUppercase")); + if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) { + if (elem->get("forceUppercase")) + mLetterCase = LetterCase::UPPERCASE; + } if (properties & LINE_SPACING) { if (elem->has("lineSpacing"))