From f463766497acba96414c5f8fb21abdf025da4c33 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 16 Apr 2022 21:54:58 +0200
Subject: [PATCH] Generalized the carousel property names and added support for
 setting media types for gamelist carousels.

---
 es-app/src/views/GamelistBase.cpp             |  30 ++++-
 es-app/src/views/GamelistView.cpp             |  21 ++++
 es-app/src/views/SystemView.cpp               |   8 +-
 es-core/src/ThemeData.cpp                     |  61 +++++----
 .../components/primary/CarouselComponent.h    | 116 ++++++++++++------
 5 files changed, 168 insertions(+), 68 deletions(-)

diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp
index 78faec534..64a630705 100644
--- a/es-app/src/views/GamelistBase.cpp
+++ b/es-app/src/views/GamelistBase.cpp
@@ -557,15 +557,43 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
 
     auto theme = mRoot->getSystem()->getTheme();
     std::string name;
+    std::string carouselItemType;
+    std::string carouselDefaultItem;
     unsigned int color {0};
 
+    if (mCarousel != nullptr) {
+        carouselItemType = mCarousel->getItemType();
+        carouselDefaultItem = mCarousel->getDefaultItem();
+    }
+
     if (files.size() > 0) {
         for (auto it = files.cbegin(); it != files.cend(); ++it) {
             if (mCarousel != nullptr) {
+                assert(carouselItemType != "");
+
                 CarouselComponent<FileData*>::Entry carouselEntry;
                 carouselEntry.name = (*it)->getName();
                 carouselEntry.object = *it;
-                carouselEntry.data.logoPath = (*it)->getMarqueePath();
+                if (carouselItemType == "" || carouselItemType == "marquee")
+                    carouselEntry.data.logoPath = (*it)->getMarqueePath();
+                else if (carouselItemType == "cover")
+                    carouselEntry.data.logoPath = (*it)->getCoverPath();
+                else if (carouselItemType == "3dbox")
+                    carouselEntry.data.logoPath = (*it)->get3DBoxPath();
+                else if (carouselItemType == "screenshot")
+                    carouselEntry.data.logoPath = (*it)->getScreenshotPath();
+                else if (carouselItemType == "titlescreen")
+                    carouselEntry.data.logoPath = (*it)->getTitleScreenPath();
+                else if (carouselItemType == "backcover")
+                    carouselEntry.data.logoPath = (*it)->getBackCoverPath();
+                else if (carouselItemType == "miximage")
+                    carouselEntry.data.logoPath = (*it)->getMiximagePath();
+                else if (carouselItemType == "fanart")
+                    carouselEntry.data.logoPath = (*it)->getFanArtPath();
+
+                if (carouselDefaultItem != "")
+                    carouselEntry.data.defaultLogoPath = carouselDefaultItem;
+
                 mCarousel->addEntry(carouselEntry, theme);
             }
 
diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp
index 685e10817..cd362d9f0 100644
--- a/es-app/src/views/GamelistView.cpp
+++ b/es-app/src/views/GamelistView.cpp
@@ -136,6 +136,27 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
             if (element.second.type == "carousel") {
                 if (mCarousel == nullptr) {
                     mCarousel = std::make_unique<CarouselComponent<FileData*>>();
+                    if (element.second.has("itemType")) {
+                        const std::string itemType {element.second.get<std::string>("itemType")};
+                        if (itemType == "marquee" || itemType == "cover" || itemType == "3dbox" ||
+                            itemType == "screenshot" || itemType == "titlescreen" ||
+                            itemType == "backcover" || itemType == "miximage" ||
+                            itemType == "fanart") {
+                            mCarousel->setItemType(itemType);
+                        }
+                        else {
+                            LOG(LogWarning)
+                                << "GamelistView::onThemeChanged(): Invalid theme configuration, "
+                                   "<itemType> property defined as \""
+                                << itemType << "\"";
+                            mCarousel->setItemType("marquee");
+                        }
+                    }
+                    else {
+                        mCarousel->setItemType("marquee");
+                    }
+                    if (element.second.has("defaultItem"))
+                        mCarousel->setDefaultItem(element.second.get<std::string>("defaultItem"));
                     mPrimary = mCarousel.get();
                 }
                 mPrimary->setCursorChangedCallback(
diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp
index b79f9f126..201871820 100644
--- a/es-app/src/views/SystemView.cpp
+++ b/es-app/src/views/SystemView.cpp
@@ -466,10 +466,10 @@ void SystemView::populate()
                             }
                         });
                         if (mCarousel != nullptr) {
-                            if (element.second.has("logo"))
-                                logoPath = element.second.get<std::string>("logo");
-                            if (element.second.has("defaultLogo"))
-                                defaultLogoPath = element.second.get<std::string>("defaultLogo");
+                            if (element.second.has("staticItem"))
+                                logoPath = element.second.get<std::string>("staticItem");
+                            if (element.second.has("defaultItem"))
+                                defaultLogoPath = element.second.get<std::string>("defaultItem");
                         }
                     }
                     else if (element.second.type == "image") {
diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp
index 09065b66b..c49471345 100644
--- a/es-core/src/ThemeData.cpp
+++ b/es-core/src/ThemeData.cpp
@@ -48,7 +48,13 @@ std::vector<std::string> ThemeData::sLegacyElements {
     {"showSnapshotDelay"},
     {"forceUppercase"},
     {"alignment"},
-    {"logoAlignment"}};
+    {"defaultLogo"},
+    {"logoSize"},
+    {"logoScale"},
+    {"logoRotation"},
+    {"logoRotationOrigin"},
+    {"logoAlignment"},
+    {"maxLogoCount"}};
 
 std::vector<std::pair<std::string, std::string>> ThemeData::sSupportedAspectRatios {
     {"16:9", "16:9"},
@@ -120,8 +126,8 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"saturation", FLOAT},
        {"visible", BOOLEAN},
        {"zIndex", FLOAT},
-       {"showSnapshotNoVideo", BOOLEAN}, // For backward compatibility with legacy themes.
-       {"showSnapshotDelay", BOOLEAN}}}, // For backward compatibility with legacy themes.
+       {"showSnapshotNoVideo", BOOLEAN},           // For backward compatibility with legacy themes.
+       {"showSnapshotDelay", BOOLEAN}}},           // For backward compatibility with legacy themes.
      {"animation",
       {{"pos", NORMALIZED_PAIR},
        {"size", NORMALIZED_PAIR},
@@ -144,7 +150,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"rotation", FLOAT},
        {"rotationOrigin", NORMALIZED_PAIR},
        {"horizontalAlignment", STRING},
-       {"alignment", STRING}, // For backward compatibility with legacy themes.
+       {"alignment", STRING},                      // For backward compatibility with legacy themes.
        {"direction", STRING},
        {"lines", UNSIGNED_INTEGER},
        {"itemsPerLine", UNSIGNED_INTEGER},
@@ -178,11 +184,11 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"fontSize", FLOAT},
        {"horizontalAlignment", STRING},
        {"verticalAlignment", STRING},
-       {"alignment", STRING}, // For backward compatibility with legacy themes.
+       {"alignment", STRING},                      // For backward compatibility with legacy themes.
        {"color", COLOR},
        {"backgroundColor", COLOR},
        {"letterCase", STRING},
-       {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
+       {"forceUppercase", BOOLEAN},                // For backward compatibility with legacy themes.
        {"lineSpacing", FLOAT},
        {"opacity", FLOAT},
        {"visible", BOOLEAN},
@@ -199,11 +205,11 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"fontSize", FLOAT},
        {"horizontalAlignment", STRING},
        {"verticalAlignment", STRING},
-       {"alignment", STRING}, // For backward compatibility with legacy themes.
+       {"alignment", STRING},                      // For backward compatibility with legacy themes.
        {"color", COLOR},
        {"backgroundColor", COLOR},
        {"letterCase", STRING},
-       {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
+       {"forceUppercase", BOOLEAN},                // For backward compatibility with legacy themes.
        {"lineSpacing", FLOAT},
        {"format", STRING},
        {"displayRelative", BOOLEAN},
@@ -222,7 +228,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"backgroundColor", COLOR},
        {"horizontalAlignment", STRING},
        {"verticalAlignment", STRING},
-       {"alignment", STRING}, // For backward compatibility with legacy themes.
+       {"alignment", STRING},                      // For backward compatibility with legacy themes.
        {"opacity", FLOAT},
        {"visible", BOOLEAN},
        {"zIndex", FLOAT}}},
@@ -247,16 +253,23 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"color", COLOR},
        {"colorEnd", COLOR},
        {"gradientType", STRING},
-       {"logo", PATH},
-       {"defaultLogo", PATH},
-       {"logoSize", NORMALIZED_PAIR},
-       {"logoScale", FLOAT},
-       {"logoRotation", FLOAT},
-       {"logoRotationOrigin", NORMALIZED_PAIR},
-       {"logoHorizontalAlignment", STRING},
-       {"logoVerticalAlignment", STRING},
-       {"logoAlignment", STRING}, // For backward compatibility with legacy themes.
-       {"maxLogoCount", FLOAT},
+       {"staticItem", PATH},
+       {"itemType", STRING},
+       {"defaultItem", PATH},
+       {"itemSize", NORMALIZED_PAIR},
+       {"itemScale", FLOAT},
+       {"itemRotation", FLOAT},
+       {"itemRotationOrigin", NORMALIZED_PAIR},
+       {"itemHorizontalAlignment", STRING},
+       {"itemVerticalAlignment", STRING},
+       {"maxItemCount", FLOAT},
+       {"defaultLogo", PATH},                      // For backward compatibility with legacy themes.
+       {"logoSize", NORMALIZED_PAIR},              // For backward compatibility with legacy themes.
+       {"logoScale", FLOAT},                       // For backward compatibility with legacy themes.
+       {"logoRotation", FLOAT},                    // For backward compatibility with legacy themes.
+       {"logoRotationOrigin", NORMALIZED_PAIR},    // For backward compatibility with legacy themes.
+       {"logoAlignment", STRING},                  // For backward compatibility with legacy themes.
+       {"maxLogoCount", FLOAT},                    // For backward compatibility with legacy themes.
        {"text", STRING},
        {"textColor", COLOR},
        {"textBackgroundColor", COLOR},
@@ -265,7 +278,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"fontSize", FLOAT},
        {"lineSpacing", FLOAT},
        {"zIndex", FLOAT},
-       {"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
+       {"legacyZIndexMode", STRING}}},             // For backward compatibility with legacy themes.
      {"textlist",
       {{"pos", NORMALIZED_PAIR},
        {"size", NORMALIZED_PAIR},
@@ -282,12 +295,12 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"secondaryColor", COLOR},
        {"fontPath", PATH},
        {"fontSize", FLOAT},
-       {"scrollSound", PATH}, // For backward compatibility with legacy themes.
+       {"scrollSound", PATH},                      // For backward compatibility with legacy themes.
        {"horizontalAlignment", STRING},
-       {"alignment", STRING}, // For backward compatibility with legacy themes.
+       {"alignment", STRING},                      // For backward compatibility with legacy themes.
        {"horizontalMargin", FLOAT},
        {"letterCase", STRING},
-       {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
+       {"forceUppercase", BOOLEAN},                // For backward compatibility with legacy themes.
        {"lineSpacing", FLOAT},
        {"indicators", STRING},
        {"collectionIndicators", STRING},
@@ -307,7 +320,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
        {"entrySpacing", FLOAT},
        {"iconTextSpacing", FLOAT},
        {"letterCase", STRING},
-       {"textStyle", STRING}, // For backward compatibility with legacy themes.
+       {"textStyle", STRING},                      // For backward compatibility with legacy themes.
        {"opacity", FLOAT},
        {"customButtonIcon", PATH}}},
      {"sound",
diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h
index 55dee1e04..af965b665 100644
--- a/es-core/src/components/primary/CarouselComponent.h
+++ b/es-core/src/components/primary/CarouselComponent.h
@@ -55,6 +55,10 @@ public:
     void addEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme = nullptr);
     Entry& getEntry(int index) { return mEntries.at(index); }
     const CarouselType getType() { return mType; }
+    const std::string& getItemType() { return mItemType; }
+    void setItemType(std::string itemType) { mItemType = itemType; }
+    const std::string& getDefaultItem() { return mDefaultItem; }
+    void setDefaultItem(std::string defaultItem) { mDefaultItem = defaultItem; }
 
     void setCursorChangedCallback(const std::function<void(CursorState state)>& func) override
     {
@@ -111,6 +115,8 @@ private:
     bool mTriggerJump;
 
     CarouselType mType;
+    std::string mItemType;
+    std::string mDefaultItem;
     std::shared_ptr<Font> mFont;
     unsigned int mTextColor;
     unsigned int mTextBackgroundColor;
@@ -547,6 +553,75 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
         }
     }
 
+    if (!theme->isLegacyTheme()) {
+        if (elem->has("itemScale"))
+            mLogoScale = glm::clamp(elem->get<float>("itemScale"), 0.5f, 3.0f);
+        if (elem->has("itemSize")) {
+            // Keep size within a 0.05 and 1.0 multiple of the screen size.
+            glm::vec2 logoSize {elem->get<glm::vec2>("itemSize")};
+            if (std::max(logoSize.x, logoSize.y) > 1.0f) {
+                logoSize /= std::max(logoSize.x, logoSize.y);
+            }
+            else if (std::min(logoSize.x, logoSize.y) < 0.005f) {
+                float ratio {std::min(logoSize.x, logoSize.y) / 0.005f};
+                logoSize /= ratio;
+                // Just an extra precaution if a crazy ratio was used.
+                logoSize.x = glm::clamp(logoSize.x, 0.005f, 1.0f);
+                logoSize.y = glm::clamp(logoSize.y, 0.005f, 1.0f);
+            }
+            mLogoSize =
+                logoSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
+        }
+
+        if (elem->has("maxItemCount"))
+            mMaxLogoCount = glm::clamp(elem->get<float>("maxItemCount"), 0.5f, 30.0f);
+
+        if (elem->has("itemRotation"))
+            mLogoRotation = elem->get<float>("itemRotation");
+        if (elem->has("itemRotationOrigin"))
+            mLogoRotationOrigin = elem->get<glm::vec2>("itemRotationOrigin");
+
+        if (elem->has("itemHorizontalAlignment")) {
+            const std::string alignment {elem->get<std::string>("itemHorizontalAlignment")};
+            if (alignment == "left" && mType != CarouselType::HORIZONTAL) {
+                mLogoHorizontalAlignment = ALIGN_LEFT;
+            }
+            else if (alignment == "right" && mType != CarouselType::HORIZONTAL) {
+                mLogoHorizontalAlignment = ALIGN_RIGHT;
+            }
+            else if (alignment == "center") {
+                mLogoHorizontalAlignment = ALIGN_CENTER;
+            }
+            else {
+                LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
+                                   "<itemHorizontalAlignment> defined as \""
+                                << alignment << "\"";
+                mLogoHorizontalAlignment = ALIGN_CENTER;
+            }
+        }
+
+        if (elem->has("itemVerticalAlignment")) {
+            const std::string alignment {elem->get<std::string>("itemVerticalAlignment")};
+            if (alignment == "top" && mType != CarouselType::VERTICAL) {
+                mLogoVerticalAlignment = ALIGN_TOP;
+            }
+            else if (alignment == "bottom" && mType != CarouselType::VERTICAL) {
+                mLogoVerticalAlignment = ALIGN_BOTTOM;
+            }
+            else if (alignment == "center") {
+                mLogoVerticalAlignment = ALIGN_CENTER;
+            }
+            else {
+                LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
+                                   "<itemVerticalAlignment> defined as \""
+                                << alignment << "\"";
+                mLogoVerticalAlignment = ALIGN_CENTER;
+            }
+        }
+    }
+
+    // Start of legacy themes only section.
+
     if (elem->has("logoScale"))
         mLogoScale = glm::clamp(elem->get<float>("logoScale"), 0.5f, 3.0f);
     if (elem->has("logoSize")) {
@@ -577,45 +652,6 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
     if (elem->has("logoRotationOrigin"))
         mLogoRotationOrigin = elem->get<glm::vec2>("logoRotationOrigin");
 
-    if (elem->has("logoHorizontalAlignment")) {
-        const std::string alignment {elem->get<std::string>("logoHorizontalAlignment")};
-        if (alignment == "left" && mType != CarouselType::HORIZONTAL) {
-            mLogoHorizontalAlignment = ALIGN_LEFT;
-        }
-        else if (alignment == "right" && mType != CarouselType::HORIZONTAL) {
-            mLogoHorizontalAlignment = ALIGN_RIGHT;
-        }
-        else if (alignment == "center") {
-            mLogoHorizontalAlignment = ALIGN_CENTER;
-        }
-        else {
-            LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
-                               "<logoHorizontalAlignment> defined as \""
-                            << alignment << "\"";
-            mLogoHorizontalAlignment = ALIGN_CENTER;
-        }
-    }
-
-    if (elem->has("logoVerticalAlignment")) {
-        const std::string alignment {elem->get<std::string>("logoVerticalAlignment")};
-        if (alignment == "top" && mType != CarouselType::VERTICAL) {
-            mLogoVerticalAlignment = ALIGN_TOP;
-        }
-        else if (alignment == "bottom" && mType != CarouselType::VERTICAL) {
-            mLogoVerticalAlignment = ALIGN_BOTTOM;
-        }
-        else if (alignment == "center") {
-            mLogoVerticalAlignment = ALIGN_CENTER;
-        }
-        else {
-            LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
-                               "<logoVerticalAlignment> defined as \""
-                            << alignment << "\"";
-            mLogoVerticalAlignment = ALIGN_CENTER;
-        }
-    }
-
-    // Legacy themes only.
     if (elem->has("logoAlignment")) {
         const std::string alignment {elem->get<std::string>("logoAlignment")};
         if (alignment == "left" && mType != CarouselType::HORIZONTAL) {
@@ -647,6 +683,8 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
         }
     }
 
+    // End of legacy theme section.
+
     mFont = Font::getFromTheme(elem, properties, mFont);
 
     if (elem->has("textColor"))