From ae96cb4c549d3409ea6a41979beaa24b4b58d2fa Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:53:02 +0200 Subject: [PATCH 01/11] Further improvements to the badges code. --- .../views/gamelist/DetailedGameListView.cpp | 27 +++-- .../src/views/gamelist/DetailedGameListView.h | 2 +- .../src/views/gamelist/VideoGameListView.cpp | 28 +++-- es-app/src/views/gamelist/VideoGameListView.h | 2 +- es-core/src/ThemeData.cpp | 10 +- es-core/src/ThemeData.h | 1 - es-core/src/components/BadgesComponent.cpp | 106 +++++++++++++++-- es-core/src/components/BadgesComponent.h | 10 +- es-core/src/components/FlexboxComponent.cpp | 111 +++++++----------- es-core/src/components/FlexboxComponent.h | 53 ++++----- 10 files changed, 214 insertions(+), 136 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index f8ab72d50..d17c7297c 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -29,7 +29,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) - , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -39,6 +38,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLastPlayed(window) , mPlayCount(window) , mName(window) + , mBadges(window) , mDescContainer(window) , mDescription(window) , mGamelistInfo(window) @@ -94,7 +94,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); - addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -103,6 +102,13 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mLblPlayCount); addChild(&mPlayCount); + // Badges. + addChild(&mBadges); + mBadges.setOrigin(0.0f, 0.0f); + mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); + mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setDefaultZIndex(50.0f); + mName.setPosition(mSize.x, mSize.y); mName.setDefaultZIndex(40.0f); mName.setColor(0xAAAAAAFF); @@ -143,6 +149,7 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); + mBadges.applyTheme(theme, getName(), "md_badges", ALL); initMDLabels(); std::vector labels = getMDLabels(); @@ -156,10 +163,10 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 9); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_badges", "md_lastplayed", "md_playcount"}; + assert(values.size() == 8); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -226,8 +233,6 @@ void DetailedGameListView::initMDValues() mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); - mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); - float bottom = 0.0f; const float colSize = (mSize.x * 0.48f) / 2.0f; @@ -297,11 +302,11 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); - mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); mPlayCount.setVisible(false); + mBadges.setVisible(false); } else { mLblRating.setVisible(true); @@ -316,11 +321,11 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); - mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); mPlayCount.setVisible(true); + mBadges.setVisible(true); } bool fadingOut = false; @@ -443,6 +448,7 @@ void DetailedGameListView::updateInfoPanel() comps.push_back(&mImage); comps.push_back(&mDescription); comps.push_back(&mName); + comps.push_back(&mBadges); std::vector labels = getMDLabels(); comps.insert(comps.cend(), labels.cbegin(), labels.cend()); @@ -488,7 +494,6 @@ std::vector DetailedGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); - ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index 356d86179..a6033b2f6 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -47,7 +47,6 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; - BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; @@ -57,6 +56,7 @@ private: DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; + BadgesComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 3f5af0828..fe4770254 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -42,10 +42,10 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) , mPublisher(window) , mGenre(window) , mPlayers(window) - , mBadges(window) , mLastPlayed(window) , mPlayCount(window) , mName(window) + , mBadges(window) , mDescContainer(window) , mDescription(window) , mGamelistInfo(window) @@ -111,7 +111,6 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); - addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -120,6 +119,13 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) addChild(&mLblPlayCount); addChild(&mPlayCount); + // Badges. + addChild(&mBadges); + mBadges.setOrigin(0.0f, 0.0f); + mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); + mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setDefaultZIndex(50.0f); + mName.setPosition(mSize.x, mSize.y); mName.setDefaultZIndex(40.0f); mName.setColor(0xAAAAAAFF); @@ -165,6 +171,7 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); + mBadges.applyTheme(theme, getName(), "md_badges", ALL); initMDLabels(); std::vector labels = getMDLabels(); @@ -178,10 +185,10 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 9); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_badges", "md_lastplayed", "md_playcount"}; + assert(values.size() == 8); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -245,9 +252,6 @@ void VideoGameListView::initMDValues() mPublisher.setFont(defaultFont); mGenre.setFont(defaultFont); mPlayers.setFont(defaultFont); - - mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); - mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); @@ -320,11 +324,11 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); - mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); mPlayCount.setVisible(false); + mBadges.setVisible(false); } else { mLblRating.setVisible(true); @@ -339,11 +343,11 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); - mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); mPlayCount.setVisible(true); + mBadges.setVisible(true); } bool fadingOut = false; @@ -484,6 +488,7 @@ void VideoGameListView::updateInfoPanel() comps.push_back(mVideo); comps.push_back(&mDescription); comps.push_back(&mName); + comps.push_back(&mBadges); std::vector labels = getMDLabels(); comps.insert(comps.cend(), labels.cbegin(), labels.cend()); @@ -526,7 +531,6 @@ std::vector VideoGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); - ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 3516d8477..f5cae00d0 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -57,10 +57,10 @@ private: TextComponent mPublisher; TextComponent mGenre; TextComponent mPlayers; - BadgesComponent mBadges; DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; + BadgesComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 0b35a30e3..339bd800c 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -150,10 +150,12 @@ std::map> The {{"pos", NORMALIZED_PAIR}, {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, - {"direction", STRING}, - {"align", STRING}, - {"itemsPerLine", FLOAT}, - {"lines", FLOAT}, + {"rotation", FLOAT}, + {"rotationOrigin", NORMALIZED_PAIR}, + {"alignment", STRING}, + {"itemsPerRow", FLOAT}, + {"rows", FLOAT}, + {"itemPlacement", STRING}, {"itemMargin", NORMALIZED_PAIR}, {"slots", STRING}, {"customBadgeIcon", PATH}, diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index ef5844e33..091b1a3ed 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -54,7 +54,6 @@ namespace ThemeFlags Z_INDEX = 8192, ROTATION = 16384, VISIBLE = 32768, - DIRECTION = 65536, ALL = 0xFFFFFFFF }; } diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 6a16f8722..bce5eaeca 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -4,30 +4,31 @@ // BadgesComponent.cpp // // Game badges icons. -// Used by gamelist views. +// Used by the gamelist views. // #define SLOT_FAVORITE "favorite" #define SLOT_COMPLETED "completed" #define SLOT_KIDGAME "kidgame" #define SLOT_BROKEN "broken" -#define SLOT_ALTERNATIVE_EMULATOR "altemulator" +#define SLOT_ALTEMULATOR "altemulator" #include "components/BadgesComponent.h" +#include "Log.h" #include "ThemeData.h" #include "utils/StringUtil.h" BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent{window, mBadgeImages} - , mBadgeTypes{ - {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}} + : GuiComponent{window} + , mFlexboxComponent{window, mBadgeImages} + , mBadgeTypes{{SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTEMULATOR}} { mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; mBadgeIcons[SLOT_KIDGAME] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; - mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemulator.svg"; + mBadgeIcons[SLOT_ALTEMULATOR] = ":/graphics/badge_altemulator.svg"; } void BadgesComponent::setBadges(const std::vector& badges) @@ -52,12 +53,27 @@ void BadgesComponent::setBadges(const std::vector& badges) // Only recalculate the flexbox if any badges changed. for (auto& image : mBadgeImages) { if (prevVisibility[image.first] != image.second.isVisible()) { - onSizeChanged(); + mFlexboxComponent.onSizeChanged(); break; } } } +void BadgesComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + if (mOpacity == 255) { + mFlexboxComponent.render(parentTrans); + } + else { + mFlexboxComponent.setOpacity(mOpacity); + mFlexboxComponent.render(parentTrans); + mFlexboxComponent.setOpacity(255); + } +} + void BadgesComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -69,6 +85,70 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; + if (elem->has("alignment")) { + const std::string alignment{elem->get("alignment")}; + if (alignment != "left" && alignment != "right") { + LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + << alignment << "\""; + } + else { + mFlexboxComponent.setAlignment(alignment); + } + } + + if (elem->has("itemsPerRow")) { + const float itemsPerRow{elem->get("itemsPerRow")}; + if (itemsPerRow < 1.0f || itemsPerRow > 10.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemsPerRow << "\""; + } + else { + mFlexboxComponent.setItemsPerLine(static_cast(itemsPerRow)); + } + } + + if (elem->has("rows")) { + const float rows{elem->get("rows")}; + if (rows < 1.0f || rows > 10.0f) { + LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + << rows << "\""; + } + else { + mFlexboxComponent.setLines(static_cast(rows)); + } + } + + if (elem->has("itemPlacement")) { + std::string itemPlacement{elem->get("itemPlacement")}; + if (itemPlacement != "top" && itemPlacement != "center" && itemPlacement != "bottom" && + itemPlacement != "stretch") { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemPlacement << "\""; + } + else { + if (itemPlacement == "top") + itemPlacement = "start"; + else if (itemPlacement == "bottom") + itemPlacement = "end"; + mFlexboxComponent.setItemPlacement(itemPlacement); + } + } + + if (elem->has("itemMargin")) { + const glm::vec2 itemMargin = elem->get("itemMargin"); + if (itemMargin.x < 0.0f || itemMargin.x > 100.0f || itemMargin.y < 0.0f || + itemMargin.y > 100.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemMargin.x << "x" << itemMargin.y << "\""; + } + else { + mFlexboxComponent.setItemMargin(itemMargin); + } + } + if (elem->has("slots")) { std::vector slots = Utils::String::delimitedStringToVector( Utils::String::toLower(elem->get("slots")), " "); @@ -89,7 +169,15 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } - // Apply theme on the flexbox component parent. - FlexboxComponent::applyTheme(theme, view, element, properties); + GuiComponent::applyTheme(theme, view, element, properties); + + mFlexboxComponent.setPosition(mPosition); + mFlexboxComponent.setSize(mSize); + mFlexboxComponent.setOrigin(mOrigin); + mFlexboxComponent.setRotation(mRotation); + mFlexboxComponent.setRotationOrigin(mRotationOrigin); + mFlexboxComponent.setVisible(mVisible); + mFlexboxComponent.setDefaultZIndex(mDefaultZIndex); + mFlexboxComponent.setZIndex(mZIndex); } } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 3b140b742..12cbd5927 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -4,15 +4,16 @@ // BadgesComponent.h // // Game badges icons. -// Used by gamelist views. +// Used by the gamelist views. // #ifndef ES_CORE_COMPONENTS_BADGES_COMPONENT_H #define ES_CORE_COMPONENTS_BADGES_COMPONENT_H #include "FlexboxComponent.h" +#include "GuiComponent.h" -class BadgesComponent : public FlexboxComponent +class BadgesComponent : public GuiComponent { public: BadgesComponent(Window* window); @@ -20,12 +21,17 @@ public: std::vector getBadgeTypes() { return mBadgeTypes; } void setBadges(const std::vector& badges); + void render(const glm::mat4& parentTrans) override; + void onSizeChanged() override { mFlexboxComponent.onSizeChanged(); } + virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; private: + FlexboxComponent mFlexboxComponent; + std::vector mBadgeTypes; std::map mBadgeIcons; std::vector> mBadgeImages; diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 95d41920f..4d921e47f 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -4,28 +4,30 @@ // FlexboxComponent.cpp // // Flexbox layout component. -// Used by gamelist views. // -#define DEFAULT_DIRECTION Direction::row -#define DEFAULT_ALIGN Align::center +#define DEFAULT_DIRECTION "row" +#define DEFAULT_ALIGNMENT "left" #define DEFAULT_ITEMS_PER_LINE 4 -#define DEFAULT_LINES 1 +#define DEFAULT_LINES 2 +#define DEFAULT_ITEM_PLACEMENT "center" #define DEFAULT_MARGIN_X 10.0f #define DEFAULT_MARGIN_Y 10.0f #include "components/FlexboxComponent.h" +#include "Settings.h" #include "ThemeData.h" FlexboxComponent::FlexboxComponent(Window* window, std::vector>& images) : GuiComponent{window} - , mDirection{DEFAULT_DIRECTION} - , mAlign{DEFAULT_ALIGN} , mImages(images) + , mDirection{DEFAULT_DIRECTION} + , mAlignment{DEFAULT_ALIGNMENT} , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} , mLines{DEFAULT_LINES} + , mItemPlacement{DEFAULT_ITEM_PLACEMENT} , mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}} , mLayoutValid{false} { @@ -36,27 +38,30 @@ void FlexboxComponent::computeLayout() // Start placing items in the top-left. float anchorX{0.0f}; float anchorY{0.0f}; - float anchorOriginX{0.0f}; - float anchorOriginY{0.0f}; // Translation directions when placing items. - glm::ivec2 directionLine{1, 0}; - glm::ivec2 directionRow{0, 1}; + glm::vec2 directionLine{1, 0}; + glm::vec2 directionRow{0, 1}; // Change direction. - if (mDirection == Direction::column) { + if (mDirection == "column") { directionLine = {0, 1}; directionRow = {1, 0}; } // Compute maximum image dimensions. glm::vec2 grid; - if (mDirection == Direction::row) + if (mDirection == "row") grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; + if (grid.x * grid.y < static_cast(mImages.size())) { + LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges " + "exceeds the product of times "; + } + // Set final image dimensions. for (auto& image : mImages) { if (!image.second.isVisible()) @@ -77,8 +82,6 @@ void FlexboxComponent::computeLayout() } // Pre-compute layout parameters. - float lineWidth = (mDirection == Direction::row ? (maxItemSize.y + mItemMargin.y) : - (maxItemSize.x + mItemMargin.x)); float anchorXStart{anchorX}; float anchorYStart{anchorY}; @@ -92,30 +95,29 @@ void FlexboxComponent::computeLayout() auto size{image.second.getSize()}; // Top-left anchor position. - float x{anchorX - anchorOriginX * size.x}; - float y{anchorY - anchorOriginY * size.y}; + float x{anchorX}; + float y{anchorY}; - // Apply alignment - if (mAlign == Align::end) { + // Apply alignment. + if (mItemPlacement == "end") { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; } - else if (mAlign == Align::center) { + else if (mItemPlacement == "center") { x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; } - else if (mAlign == Align::stretch && mDirection == Direction::row) { + else if (mItemPlacement == "stretch" && mDirection == "row") { image.second.setSize(image.second.getSize().x, maxItemSize.y); } - // Apply origin. - if (mOrigin.x > 0 && mOrigin.x <= 1) - x -= mOrigin.x * mSize.x; - if (mOrigin.y > 0 && mOrigin.y <= 1) - y -= mOrigin.y * mSize.y; + // TODO: Doesn't work correctly. + // Apply overall container alignment. + if (mAlignment == "right") + x += (mSize.x - size.x * grid.x) - mItemMargin.x; // Store final item position. - image.second.setPosition(getPosition().x + x, getPosition().y + y); + image.second.setPosition(x, y); // Translate anchor. if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { @@ -126,11 +128,11 @@ void FlexboxComponent::computeLayout() else { // Translate to first position of next line. if (directionRow.x == 0) { - anchorY += lineWidth * static_cast(directionRow.y); + anchorY += size.y + mItemMargin.y; anchorX = anchorXStart; } else { - anchorX += lineWidth * static_cast(directionRow.x); + anchorX += size.x + mItemMargin.x; anchorY = anchorYStart; } } @@ -147,47 +149,20 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) if (!mLayoutValid) computeLayout(); - for (auto& image : mImages) - image.second.render(parentTrans); -} + glm::mat4 trans{parentTrans * getTransform()}; + Renderer::setMatrix(trans); -void FlexboxComponent::applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) -{ - using namespace ThemeFlags; + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); - glm::vec2 scale{getParent() ? getParent()->getSize() : - glm::vec2{static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}}; - - const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); - if (!elem) - return; - - if (properties & DIRECTION && elem->has("direction")) - mDirection = - elem->get("direction") == "row" ? Direction::row : Direction::column; - - if (elem->has("align")) { - const auto a = elem->get("align"); - mAlign = (a == "start" ? - Align::start : - (a == "end" ? Align::end : (a == "center" ? Align::center : Align::stretch))); + for (auto& image : mImages) { + if (mOpacity == 255) { + image.second.render(trans); + } + else { + image.second.setOpacity(mOpacity); + image.second.render(trans); + image.second.setOpacity(255); + } } - - if (elem->has("itemsPerLine")) - mItemsPerLine = elem->get("itemsPerLine"); - - if (elem->has("lines")) - mLines = elem->get("lines"); - - if (elem->has("itemMargin")) - mItemMargin = elem->get("itemMargin") * scale; - - GuiComponent::applyTheme(theme, view, element, properties); - - // Layout no longer valid. - mLayoutValid = false; } diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index c278b1aed..bbe385aba 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -4,7 +4,6 @@ // FlexboxComponent.h // // Flexbox layout component. -// Used by gamelist views. // #ifndef ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H @@ -16,26 +15,21 @@ class FlexboxComponent : public GuiComponent { public: - enum class Direction : char { - row, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). - column - }; - - enum class Align : char { - start, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). - end, - center, - stretch - }; - explicit FlexboxComponent(Window* window, std::vector>& images); - // Getters/Setters for rendering options. - Align getAlign() const { return mAlign; } - void setAlign(Align value) + // Getters/setters for the layout. + void setDirection(const std::string& direction) { - mAlign = value; + assert(direction == "row" || direction == "column"); + mDirection = direction; + } + + std::string getAlignment() const { return mAlignment; } + void setAlignment(const std::string& value) + { + assert(value == "left" || value == "right"); + mAlignment = value; mLayoutValid = false; } @@ -53,34 +47,39 @@ public: mLayoutValid = false; } + std::string getItemPlacement() const { return mItemPlacement; } + void setItemPlacement(const std::string& value) + { + assert(value == "start" || value == "center" || value == "end" || value == "stretch"); + mItemPlacement = value; + mLayoutValid = false; + } + glm::vec2 getItemMargin() const { return mItemMargin; } void setItemMargin(glm::vec2 value) { - mItemMargin = value; + mItemMargin.x = std::roundf(value.x * Renderer::getScreenWidth()); + mItemMargin.y = std::roundf(value.y * Renderer::getScreenHeight()); mLayoutValid = false; } void onSizeChanged() override { mLayoutValid = false; } void render(const glm::mat4& parentTrans) override; - void applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) override; private: // Calculate flexbox layout. void computeLayout(); - // Layout options. - Direction mDirection; - Align mAlign; - std::vector>& mImages; + // Layout options. + std::string mDirection; + std::string mAlignment; unsigned int mItemsPerLine; unsigned int mLines; - + std::string mItemPlacement; glm::vec2 mItemMargin; + bool mLayoutValid; }; From 47242760182e7ac0c56c4bc6b4074404af66066b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:55:44 +0200 Subject: [PATCH 02/11] (rbsimple-DE) Update to align with the changes to BadgesComponent. --- themes/rbsimple-DE/theme.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index bda55e899..c976a96fc 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -240,10 +240,9 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.675 0.15 0.21 0 0 - row - start - 3 - 2 + left + 3 + 2 0.0028125 0.005 favorite completed kidgame broken altemulator From cb2b26657beb23a989309b4270b709a98c5988f6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:58:30 +0200 Subject: [PATCH 03/11] Updated the badges documentation. --- THEMES-DEV.md | 291 +++++++++++++++++++++++++------------------------- 1 file changed, 144 insertions(+), 147 deletions(-) diff --git a/THEMES-DEV.md b/THEMES-DEV.md index fc6c82f57..b2a659cf0 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -1,83 +1,90 @@ # EmulationStation Desktop Edition (ES-DE) v1.2 (development version) - Themes -**Note:** If creating theme sets specifically for ES-DE, please add `-DE` to the theme name, as in `rbsimple-DE`. Because the ES-DE theme support has already deviated somehow from the RetroPie EmulationStation fork and will continue to deviate further in the future, the theme set will likely not be backwards compatible. It would be confusing and annoying for a user that downloads and attempts to use an ES-DE theme set in another EmulationStation fork only to get crashes, error messages or corrupted graphics. At least the -DE extension is a visual indicator that it's an ES-DE specific theme set. +**Note:** This document is only relevant for the current ES-DE development version, if you would like to see the documentation for the latest stable release, refer to [THEMES.md](THEMES.md) instead. -Also note that this document is only relevant for the current ES-DE development version, if you would like to see the documentation for the latest stable release, refer to [THEMES.md](THEMES.md) instead. +If creating theme sets specifically for ES-DE, please add `-DE` to the theme name, as in `rbsimple-DE`. Because the ES-DE theme support has already deviated somehow from the RetroPie EmulationStation fork and will continue to deviate further in the future, the theme set will likely not be backwards compatible. It would be confusing and annoying for a user that downloads and attempts to use an ES-DE theme set in another EmulationStation fork only to get crashes, error messages or corrupted graphics. At least the -DE extension is a visual indicator that it's an ES-DE specific theme set. -ES-DE allows each system to have its own "theme." A theme is a collection **views** that define some **elements**, each with their own **properties**. +Table of contents: -The first place ES-DE will check for a theme is in the system's `` folder, for a theme.xml file: -* `[SYSTEM_PATH]/theme.xml` +[[_TOC_]] -If that file doesn't exist, ES-DE will try to find the theme in the current **theme set**. Theme sets are just a collection of individual system themes arranged in the "themes" folder under some name. A theme set can provide a default theme that will be used if there is no matching system theme. Here's an example: +## Introduction + +ES-DE allows the grouping of themes for multiple game systems into a **theme set**. Each theme is a collection of **views** that define some **elements**, each with their own **properties**. + +Every game system has its own subdirectory within the theme set directory structure, and these are defined in the systems configuration file `es_systems.xml` either via the optional `` tag, or otherwise via the mandatory `` tag. When ES-DE populates a system on startup it will look for a file named `theme.xml` in each such directory. + +By placing a theme.xml file directly in the root of the theme set directory, that file will be processed as a default if there is no game-specific theme.xml file available. + +In the example below, we have a theme set named `mythemeset-DE` which includes the `snes` and `nes` systems. Assuming you have some games installed for these systems, the files `mythemeset-DE/nes/theme.xml` and `mythemeset-DE/snes/theme.xml` will be processed on startup. If there are no games available for a system, its theme.xml file will be skipped. + +The directory structure of our example theme set could look something like the following: ``` ... themes/ - my_theme_set/ - snes/ - theme.xml - my_cool_background.jpg + mythemeset-DE/ + core/ + font.ttf + bold_font.ttf + frame.png nes/ theme.xml - my_other_super_cool_background.jpg + background.jpg + logo.svg + logo_video.svg - common_resources/ - my_font.ttf - - theme.xml (Default theme) - another_theme_set/ snes/ theme.xml - some_resource.svg + background.jpg + logo.svg + logo_video.svg + + fonts.xml + theme.xml ``` -The theme set system makes it easy for users to try different themes and allows distributions to include multiple theme options. Users can change the currently active theme set in the "UI Settings" menu. The option is only visible if at least one theme set exists. +The theme set approach makes it easy for users to install different themes and choose between them from the _UI Settings_ menu. -There are two places ES-DE can load theme sets from: -* `[HOME]/.emulationstation/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` -* `[INSTALLATION PATH]/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` +There are two places that ES-DE can load theme sets from: +* `[HOME]/.emulationstation/themes/[THEME_SET]/` +* `[INSTALLATION PATH]/themes/[THEME_SET]/` An example installation path would be: \ -`/usr/share/emulationstation/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` +`/usr/share/emulationstation/themes/rbsimple-DE/` -`[SYSTEM_THEME]` is the `` tag for the system, as defined in `es_systems.cfg`. If the `` tag is not set, ES-DE will use the system's ``. +If a theme set with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped. -If both files happen to exist, ES-DE will pick the first one (the one located in the home directory). +## Simple example -Again, the `[CURRENT_THEME_SET]` value is set in the "UI Settings" menu. If it has not been set yet or the previously selected theme set is missing, the first available theme set will be used as the default. - -# Simple Example - -Here is a very simple theme that changes the description text's color: +Here is a very simple theme that changes the color of the game description text: ```xml - 7 00FF00 - + 0.5 0.5 0.5 0.5 0.8 0.8 - ./my_art/my_awesome_image.jpg + ./core/frame.png ``` -# How it works +## How it works -Everything must be inside a `` tag. +All configuration must be contained within a `` tag pair. -**The `` tag *must* be specified**. This is the version of the theming system the theme was designed for. +The `` tag **must** be specified. This is the version of the theming system the theme was designed for. The current version is 7. -A *view* can be thought of as a particular "screen" within EmulationStation. Views are defined like this: +A _view_ can be thought of as a particular "screen" within ES-DE. Views are defined like this: ```xml @@ -85,63 +92,61 @@ A *view* can be thought of as a particular "screen" within EmulationStation. Vie ``` - - -An *element* is a particular visual element, such as an image or a piece of text. You can either modify an element that already exists for a particular view (as is done in the "description" example), like this: +An *element* is a particular visual element, such as an image or a piece of text. You can modify an element that already exists for a particular view, as was done for the "description" example: ```xml - - ... define properties here ... - + + ... define properties here ... + ``` -Or, you can create your own elements by adding `extra="true"` (as is done in the "my_image" example) like this: +Or you can create your own elements by adding `extra="true"`, as was done for the "frame" example: ```xml - - ... define properties here ... - + + ... define properties here ... + ``` -"Extra" elements will be drawn in the order they are defined (so define backgrounds first!). When they get drawn relative to the pre-existing elements depends on the view. Make sure "extra" element names do not clash with existing element names! An easy way to protect against this is to just start all your extra element names with some prefix like "e_". +"Extra" elements will be drawn in the order they are defined (so make sure to define backgrounds first). In what order they get drawn relative to the pre-existing elements depends on the view. Make sure "extra" element names do not clash with existing element names. An easy way to protect against this is to start all your extra element names with a prefix such as "e_". -*Properties* control how a particular *element* looks - for example, its position, size, image path, etc. The type of the property determines what kinds of values you can use. You can read about the types below in the "Reference" section. Properties are defined like this: +*Properties* control how a particular *element* looks - for example its position, size, image path etc. The type of the property determines what kinds of values you can use. You can read about the types below in the "Reference" section. Properties are defined like this: ```xml - ValueHere +ValueHere ``` -# Advanced Features +## Advanced features -It is recommended that if you are writing a theme you launch EmulationStation with the `--debug` and `--windowed` switches. This way you can read error messages without having to check the log file. You can also reload the current gamelist view and system view with `Ctrl-R` if `--debug` is specified. +If you are writing a theme it's recommended to launch ES-DE with the `--debug` flag from a terminal window. If on Unix you can also pass the `--windowed` and `--resolution` flags to avoid having the application window fill the entire screen. On macOS and Windows you only need to pass the `--resolution` flag to accomplish this. By doing so, you can read error messages directly in the terminal window without having to open the log file. You can also reload the current gamelist view and system view with `Ctrl-R` if the `--debug` flag has been set. -### The `` tag +### The \ tag -You can include theme files within theme files, similar to `#include` in C (though the internal mechanism is different, the effect is the same). Example: +You can include theme files within theme files, for example: -`~/.emulationstation/all_themes.xml`: +`~/.emulationstation/themes/mythemeset-DE/fonts.xml`: ```xml - 7 - ./all_themes/myfont.ttf + ./core/font.ttf + 0.035 00FF00 ``` -`~/.emulationstation/snes/theme.xml`: +`~/.emulationstation/themes/mythemeset-DE/snes/theme.xml`: ```xml 7 - ./../all_themes.xml + ./../fonts.xml FF0000 @@ -150,84 +155,77 @@ You can include theme files within theme files, similar to `#include` in C (thou ``` -Is equivalent to this `snes/theme.xml`: +The above is equivalent to the following: ```xml 7 - ./all_themes/myfont.ttf + ./core/font.ttf + 0.035 FF0000 ``` -Notice that properties that were not specified got merged (``) and the `snes/theme.xml` could overwrite the included files' values (``). Also notice the included file still needed the `` tag. +Note that properties can get merged. In the above example the `` tag from `fonts.xml` got overwritten by the equivalent tag in `snes/theme.xml`. This happens because that tag was effectively declared after the first `` tag. Be aware that the included file also needs the `` tag. ### Theming multiple views simultaneously -Sometimes you want to apply the same properties to the same elements across multiple views. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). So, for example, to easily apply the same header to the basic, grid, and system views: +Sometimes you want to apply the same properties to the same elements across multiple views. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). So for example, to apply the same logo to the basic and detailed views you could write the following: ```xml - 7 - + - ./snes_art/snes_header.png + ./snes/logo.svg - + - ./snes_art/snes_header_detailed.png + ./snes/logo_video.svg ``` -This is equivalent to: -```xml +The above is equivalent to: +```xml 7 - ./snes_art/snes_header.png + ./snes/logo.svg - ./snes_art/snes_header_detailed.png + ./snes/logo.svg - + - ./snes_art/snes_header.png + ./snes/logo_video.svg - - - ./snes_art/snes_header.png - - - ... and any other view that might try to look up "logo" ... ``` ### Theming multiple elements simultaneously -You can theme multiple elements *of the same type* simultaneously. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas), just like it does for views, as long as the elements have the same type. This is useful if you want to, say, apply the same color to all the metadata labels: +You can theme multiple elements *of the same type* simultaneously. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). This is useful if you want to, say, apply the same color to all the metadata labels: ```xml - 7 - + 48474D @@ -238,7 +236,6 @@ You can theme multiple elements *of the same type* simultaneously. The `name` a Which is equivalent to: ```xml - 7 @@ -270,20 +267,16 @@ Which is equivalent to: ``` -Just remember, *this only works if the elements have the same type!* - -### Element rendering order with z-index - -You can now change the order in which elements are rendered by setting `zIndex` values. Default values correspond to the default rendering order while allowing elements to easily be shifted without having to set `zIndex` values for every element. Elements will be rendered in order from smallest z-index to largest. +Just remember, _this only works if the elements have the same type._ -#### Navigation sounds +### Navigation sounds -The navigation sounds are configured globally per theme set, so it needs to be defined as a feature and with the view set to the special 'all' category. +Navigation sounds are configured globally per theme set, so it needs to be defined as a feature and with the view set to the special "all" category. It's recommended to put these elements in a separate file and include it from the main theme file (e.g. `./navigationsounds.xml`). There are seven different navigation sounds that can be configured. The names as well as the element structure should be self-explanatory based on the example below. -Starting EmulationStation with the --debug flag will provide feedback on whether any navigation sound elements were read from the theme set. If no navigation sound is provided by the theme, ES-DE will use the bundled navigation sound file as a fallback. This is done per sound, so the theme could provide for example one or two custom sound files while using the bundled ES-DE sounds for the other samples. +Starting ES-DE with the --debug flag will provide feedback on whether any navigation sound elements were read from the theme set. If no navigation sounds are provided by the theme, ES-DE will use the bundled navigation sounds as a fallback. This is done per sound file, so the theme could provide for example one or two custom sounds while using the bundled ES-DE sounds for the rest. Example debug output: ``` @@ -297,7 +290,6 @@ Jul 12 11:28:58 Debug: Sound::getFromTheme(): Tag not found, using fallback sou Example `navigationsounds.xml`, to be included from the main theme file: ```xml - 7 @@ -328,14 +320,18 @@ Example `navigationsounds.xml`, to be included from the main theme file: ``` -#### Defaults +### Element rendering order using zIndex -##### system +You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. + +Below are the default zIndex values per element type: + +#### system * Extra Elements `extra="true"` - 10 * `carousel name="systemcarousel"` - 40 * `text name="systemInfo"` - 50 -##### basic, detailed, grid, video +#### basic, detailed, video, grid * `image name="background"` - 0 * Extra Elements `extra="true"` - 10 * `textlist name="gamelist"` - 20 @@ -370,21 +366,23 @@ Example `navigationsounds.xml`, to be included from the main theme file: * `image name="logo"` * Gamelist information - 50 * `text name="gamelistInfo"` +* Badges - 50 + * `badges name="md_badges"` ### Theme variables Theme variables can be used to simplify theme construction. There are 2 types of variables available. -* System Variables -* Theme Defined Variables +* System variables +* Theme defined variables -#### System Variables +#### System variables -System variables are system specific and are derived from the values in es_systems.cfg. +System variables are system specific and are derived from the values in es_systems.xml. * `system.name` * `system.fullName` * `system.theme` -#### Theme Defined Variables +#### Theme defined variables Variables can also be defined in the theme. ``` @@ -406,9 +404,23 @@ or to specify only a portion of the value of a theme property: ```` -# Reference +## Reference + +### Views, their elements, and themeable properties + +#### system +* `helpsystem name="help"` - ALL + - The help system style for this view. +* `carousel name="systemcarousel"` -ALL + - The system logo carousel +* `image name="logo"` - PATH | COLOR + - A logo image, to be displayed in the system logo carousel. +* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT + - A logo text, to be displayed system name in the system logo carousel when no logo is available. +* `text name="systemInfo"` - ALL + - Displays details of the system currently selected in the carousel. +* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed behind the carousel, and scroll relative to the carousel. -## Views, their elements, and themeable properties: #### basic * `helpsystem name="help"` - ALL @@ -422,7 +434,6 @@ or to specify only a portion of the value of a theme property: * `textlist name="gamelist"` - ALL - The gamelist. `primaryColor` is for games, `secondaryColor` is for folders. Centered by default. ---- #### detailed * `helpsystem name="help"` - ALL @@ -438,8 +449,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -450,8 +461,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `image name="md_image"` - POSITION | SIZE | Z_INDEX - Path is the "image" metadata for the currently selected game. * `rating name="md_rating"` - ALL @@ -467,7 +477,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -477,6 +487,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default + #### video * `helpsystem name="help"` - ALL - The help system style for this view. @@ -491,8 +502,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -503,8 +514,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `image name="md_image"` - POSITION | SIZE | Z_INDEX - Path is the "image" metadata for the currently selected game. * `image name="md_marquee"` - POSITION | SIZE | Z_INDEX @@ -524,7 +534,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -534,7 +544,6 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default ---- #### grid * `helpsystem name="help"` - ALL @@ -554,8 +563,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -566,8 +575,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `rating name="md_rating"` - ALL - The "rating" metadata. * `datetime name="md_releasedate"` - ALL @@ -581,7 +589,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -591,23 +599,8 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default ---- -#### system -* `helpsystem name="help"` - ALL - - The help system style for this view. -* `carousel name="systemcarousel"` -ALL - - The system logo carousel -* `image name="logo"` - PATH | COLOR - - A logo image, to be displayed in the system logo carousel. -* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT - - A logo text, to be displayed system name in the system logo carousel when no logo is available. -* `text name="systemInfo"` - ALL - - Displays details of the system currently selected in the carousel. -* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed behind the carousel, and scroll relative to the carousel. - - -## Types of properties: +### Types of properties * NORMALIZED_PAIR - two decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5`. Most commonly used for position (x and y coordinates) and size (width and height). * NORMALIZED_RECT - four decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5 0.10 0.30`. Most commonly used for padding to store top, left, bottom and right coordinates. @@ -618,7 +611,7 @@ or to specify only a portion of the value of a theme property: * STRING - a string of text. -## Types of elements and their properties: +### Types of elements and their properties Common to almost all elements is a `pos` and `size` property of the NORMALIZED_PAIR type. They are normalized in terms of their "parent" object's size; 99% of the time, this is just the size of the screen. In this case, `0 0` would correspond to the top left corner, and `1 1` the bottom right corner (a positive Y value points further down). `pos` almost always refers to the top left corner of your element. You *can* use numbers outside of the [0..1] range if you want to place an element partially or completely off-screen. @@ -803,7 +796,7 @@ Can be created as an extra. * `zIndex` - type: FLOAT. - z-index value for component. Components will be rendered in order of z-index value from low to high. -EmulationStation borrows the concept of "nine patches" from Android (or "9-Slices"). Currently the implementation is very simple and hard-coded to only use 48x48px images (16x16px for each "patch"). Check the `data/resources` directory for some examples (button.png, frame.png). +ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Currently the implementation is very simple and hard-coded to only use 48x48px images (16x16px for each "patch"). Check the `data/resources` directory for some examples (button.png, frame.png). #### rating @@ -923,19 +916,21 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. * `origin` - type: NORMALIZED_PAIR. - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. -* `direction` - type: STRING. - - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. -* `align` - type: STRING. - - Valid values are "start", "center", "end", or "stretch". Controls orthogonal alignment to the line axis. "stretch" will stretch the badges to fill-up the line width. -* `itemsPerLine` - type: FLOAT. - - Number of badges that fit on a line. When more badges are available a new line will be started. -* `lines` - type: FLOAT. - - The number of lines available. +* `rotation` - type: FLOAT. + - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. +* `rotationOrigin` - type: NORMALIZED_PAIR. + - Point around which the image will be rotated. Defaults to `0.5 0.5`. +* `itemsPerRow` - type: FLOAT. + - Number of badges that fit on a row. When more badges are available a new row will be started. +* `rows` - type: FLOAT. + - The number of rows available. +* `itemPlacement` - type: STRING. + - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - `x y` - horizontal and vertical margins. * `slots` - type: STRING. - - The badge types that should be displayed. Should be specified as a dist of strings separated by spaces. The order will be followed when placing badges on the screen. + - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. - Available badges are: - "favorite": Will be shown when the game is marked as favorite. - "completed": Will be shown when the game is marked as completed. @@ -992,7 +987,9 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. -To see some examples of EmulationStation themes, the following resources are recommended: +## Example theme sets + +To see some example EmulationStation themes, the following resources are recommended: https://aloshi.com/emulationstation#themes From 8ec17dbaee667baa960823bf409dda0f8a7e926d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:27:48 +0200 Subject: [PATCH 04/11] Fixed a very minor line break issue. --- es-app/src/guis/GuiScreensaverOptions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/es-app/src/guis/GuiScreensaverOptions.cpp b/es-app/src/guis/GuiScreensaverOptions.cpp index 8c7dd71ca..ea6eaf037 100644 --- a/es-app/src/guis/GuiScreensaverOptions.cpp +++ b/es-app/src/guis/GuiScreensaverOptions.cpp @@ -52,7 +52,8 @@ GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& // If before it wasn't risky but now there's a risk of problems, show warning. mWindow->pushGui(new GuiMsgBox( mWindow, getHelpStyle(), - "THE 'VIDEO' SCREENSAVER SHOWS\nVIDEOS FROM YOUR GAMELISTS\n\n" + "THE 'VIDEO' SCREENSAVER SHOWS\n" + "VIDEOS FROM YOUR GAMELISTS\n\n" "IF YOU DO NOT HAVE ANY VIDEOS, THE\n" "SCREENSAVER WILL DEFAULT TO 'DIM'", "OK", [] { return; }, "", nullptr, "", nullptr)); From b9b4bd120d357a5e27339a5a07b60ca37c5b3fc4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:32:26 +0200 Subject: [PATCH 05/11] Fixed multiple issues where ComponentGrid would display incorrect help prompts. --- es-core/src/components/ComponentGrid.cpp | 44 +++++++++++++++++------- es-core/src/guis/GuiMsgBox.cpp | 5 --- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/es-core/src/components/ComponentGrid.cpp b/es-core/src/components/ComponentGrid.cpp index fd87cd10d..1fd3c4c7e 100644 --- a/es-core/src/components/ComponentGrid.cpp +++ b/es-core/src/components/ComponentGrid.cpp @@ -474,22 +474,42 @@ std::vector ComponentGrid::getHelpPrompts() if (e) prompts = e->component->getHelpPrompts(); - bool canScrollVert = mGridSize.y > 1; - bool canScrollHoriz = mGridSize.x > 1; - for (auto it = prompts.cbegin(); it != prompts.cend(); it++) { - if (it->first == "up/down/left/right") { - canScrollHoriz = false; - canScrollVert = false; - break; + bool canScrollVert = false; + + // If the currently selected cell does not fill the entire Y axis, then check if the cells + // above or below are actually focusable as otherwise they should not affect the help prompts. + if (mGridSize.y > 1 && e->dim.y < mGridSize.y) { + if (e->pos.y - e->dim.y >= 0) { + const GridEntry* cell = getCellAt(glm::ivec2{e->pos.x, e->pos.y - e->dim.y}); + if (cell != nullptr && cell->canFocus) + canScrollVert = true; } - else if (it->first == "up/down") { - canScrollVert = false; - } - else if (it->first == "left/right") { - canScrollHoriz = false; + if (e->pos.y + e->dim.y < mGridSize.y) { + const GridEntry* cell = getCellAt(glm::ivec2{e->pos.x, e->pos.y + e->dim.y}); + if (cell != nullptr && cell->canFocus) + canScrollVert = true; } } + // There is currently no situation in the application where unfocusable cells are located + // next to each other horizontally, so this code is good enough. If this changes in the + // future, code similar to the the vertical cell handling above needs to be added. + bool canScrollHoriz = (mGridSize.x > 1 && e->dim.x < mGridSize.x); + + // Check existing capabilities as indicated by the help prompts, and if the prompts should + // be combined into "up/down/left/right" then also remove the single-axis prompts. + if (!prompts.empty() && prompts.back() == HelpPrompt("up/down", "choose")) { + canScrollVert = true; + if (canScrollHoriz && canScrollVert) + prompts.pop_back(); + } + else if (!prompts.empty() && prompts.back() == HelpPrompt("left/right", "choose")) { + canScrollHoriz = true; + if (canScrollHoriz && canScrollVert) + prompts.pop_back(); + } + + // Any duplicates will be removed in Window::setHelpPrompts() if (canScrollHoriz && canScrollVert) prompts.push_back(HelpPrompt("up/down/left/right", "choose")); else if (canScrollHoriz) diff --git a/es-core/src/guis/GuiMsgBox.cpp b/es-core/src/guis/GuiMsgBox.cpp index 0e0a7fcb2..d92acbb9b 100644 --- a/es-core/src/guis/GuiMsgBox.cpp +++ b/es-core/src/guis/GuiMsgBox.cpp @@ -179,11 +179,6 @@ std::vector GuiMsgBox::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - // If there is only one button, then remove the "Choose" help symbol - // as there is no way to make a choice. - if (mButtons.size() == 1) - prompts.pop_back(); - if (!mDisableBackButton) prompts.push_back(HelpPrompt("b", "Back")); From 5b2725ba76104c3ca2e613d58327a9f99146f961 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:40:58 +0200 Subject: [PATCH 06/11] Documentation update. --- CHANGELOG.md | 9 +++++++++ CONTRIBUTING.md | 8 ++++---- USERGUIDE-DEV.md | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3af834783..5d7e306b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,10 @@ * Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected system-wide or per game via the user interface * Populated the bundled es_systems.xml files with alternative emulator entries for most RetroArch cores * Added a virtual keyboard, partly based on code from batocera-emulationstation +* Added badges that indicate favorite/completed/broken games as well as games suitable for children and those with a selected alternative emulator * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting +* Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen * Improved the gamelist filter screen to not allow filtering of values where there is no actual data to filter, e.g. Favorites for a system with no favorite games * Grayed out all fields in the gamelist filter screen where there is no data to filter, previously some fields were removed entirely and some could still be used * Added the ability to filter on blank/unknown values for Genre, Player, Developer, Publisher and Alternative emulator. @@ -25,6 +27,7 @@ * Made the scrolling speed of ScrollableContainer more consistent across various screen resolutions and display aspect ratios * Made the game name and description stop scrolling when running the media viewer, the screensaver or when running in the background while a game is launched * Added notification popups when plugging in or removing controllers +* Changed to loading the default theme set rbsimple-DE instead of the first available theme if the currently configured theme is missing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" @@ -35,6 +38,7 @@ * Added a blinking cursor to TextEditComponent * Changed the filter description "Text filter (game name)" to "Game name" * Added support for multi-select total count and exclusive multi-select to OptionListComponent +* Added support for a maximum name length to OptionListComponent (non-multiselect only) with an abbreviation of the name if it exceeds this value * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button * Added key repeat for the "Jump to" and "Sort games by" selectors on the game options menu * Added key repeat when editing the "Release date" entry in the metadata editor (DateTimeEditComponent) @@ -74,14 +78,19 @@ * Leading and trailing whitespace characters would get included in scraper search refines and TheGamesDB searches * Game name (text) filters were matching the system names for collection systems if the "Show system names in collections" setting was enabled * Brackets such as () and [] were filtered from game names in collection systems if the "Show system names in collections" setting was enabled +* Fixed multiple issues where ComponentGrid would display incorrect help prompts * Help prompts were missing for the "Rating" and "Release date" fields in the metadata editor * There was some strange behavior in DateTimeEditComponent when changing the date all the way down to 1970-01-01 * When navigating menus, the separator lines and menu components did not align properly and moved up and down slightly +* Under some circumstances and at some screen resolutions, the last menu separator line would not get rendered (still an issue at extreme resolutions like 320x240) * When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior * With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size +* If there was an abbreviated full system name for the "Gamelist on startup" option, that abbreviation would also get displayed when opening the selector window +* Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set * When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed * Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions +* The sizing of the metadata editor was strange, which was clearly visible when activating the Ctrl+G debug mode * The "sortname" window header was incorrectly spelled when editing this type of entry in the metadata editor * When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a button below the list diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45d8f5725..fe83c2b6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,6 @@ This plan is under constant review so expect it to change from time to time. Sti * Support for pre-defined alternative emulators and cores (configured in es_systems.xml) * Badges highlighting things like favorite games, completed games etc. (will require theme support) -* Improved full-screen support, removing the temporary full-screen hacks * Virtual (on-screen) keyboard * Support for the Raspberry Pi 4 (Raspberry Pi OS) * Add GLM library dependency for matrix and vector operations, decommission the built-in functions @@ -39,21 +38,22 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.3 -* Localization/multi-language support * New theme engine with generalized views (only System and Gamelist) and theme variants support * Add multiple new gamelist components (wheels, wall/grid etc.) * Move existing theme logic to legacy support, only to be used for backwards compatibility +* Improve full-screen support and make game launching more seamless, remove the temporary full-screen hacks * Checksum support for the scraper for exact searches and for determining when to overwrite files -* Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing +* Improve text and font functions, e.g. faster and cleaner line wrapping and more exact sizing #### v1.4 +* Localization/multi-language support * Authoring tools to clean up orphaned gamelist entries, media files etc. * Scrollbar component for the gamelist view which can be used by the themes * Web proxy support for the scraper * Add "time played" counter per game, similar to how it works in Steam * Preload all built-in resources and never clear them from the cache -* Improved multi-threading +* Improve multi-threading #### v1.5 diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 25ffbd132..34d760fd6 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -970,6 +970,10 @@ With this option enabled, there will be an overlay displayed when scrolling the This enables a virtual (on-screen) keyboard that can be used at various places throughout the application to input text and numbers using a controller. The Shift and Alt keys can be toggled individually or combined together to access many special characters. The general use of the virtual keyboard should hopefully be self-explanatory. +**Enable menu scroll indicators** + +With this option enabled, "up and down" scroll indicators will be displayed in the upper right corner of menus (including the metadata editor) if there are more entries available than can be shown on the screen at the same time. These indicators will change dynamically as the list is scrolled. If the setting is disabled, a simplified static indicator will be displayed instead. + **Enable toggle favorites button** This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button to add or remove games when editing custom collections. From cb44762537778a08b16b7eacd9fc3ab7d1d79ce9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:19:37 +0200 Subject: [PATCH 07/11] Made it possible to mark folders with the Kidgame metadata flag. --- es-app/src/MetaData.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 18b3557bb..98a354e59 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -42,21 +42,22 @@ MetaDataDecl gameDecls[] = { }; MetaDataDecl folderDecls[] = { -{"name", MD_STRING, "", false, "name", "enter name", true}, -{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, -{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, -{"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true}, -{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, -{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, -{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, -{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, -{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, -{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, -{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, -{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, -{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, -{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, -{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} +{"name", MD_STRING, "", false, "name", "enter name", true}, +{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, +{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, +{"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true}, +{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, +{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, +{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, +{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, +{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, +{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, +{"kidgame", MD_BOOL, "false", false, "kidgame (only affects badges)", "enter kidgame off/on", false}, +{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, +{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, +{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, +{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, +{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; // clang-format on From 07425d41fabb98db56759006657df848c02b04cc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:22:29 +0200 Subject: [PATCH 08/11] Fixed an issue with removing invalid alternative emulator entries using the metadata editor. --- es-app/src/guis/GuiMetaDataEd.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index ced319e6c..cf12889a7 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -248,6 +248,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); for (auto entry : launchCommands) { + if (mInvalidEmulatorEntry && singleEntry && + entry.second != + ViewController::EXCLAMATION_CHAR + " " + originalValue) + continue; + std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; From c2d059f92e5e3e5f7516ef0a0df48a1602c67985 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:23:29 +0200 Subject: [PATCH 09/11] (Windows) Fixed two MSVC compiler warnings. --- es-app/src/views/gamelist/DetailedGameListView.cpp | 2 +- es-app/src/views/gamelist/VideoGameListView.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 35808dd29..b343e8b3c 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -106,7 +106,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mBadges); mBadges.setOrigin(0.0f, 0.0f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); - mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); mName.setPosition(mSize.x, mSize.y); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 6e98be2e1..c0f1c1bc4 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -123,7 +123,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) addChild(&mBadges); mBadges.setOrigin(0.0f, 0.0f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); - mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); mName.setPosition(mSize.x, mSize.y); From eb611d12dbe8bdb24fb976a79d5c3ea5f6cbc6e5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 18:18:23 +0200 Subject: [PATCH 10/11] Fixed some issues in FlexboxComponent. Also added some sanity checks and size restrictions to BadgeComponent and FlexboxComponent. --- es-core/src/components/BadgesComponent.cpp | 6 +- es-core/src/components/FlexboxComponent.cpp | 87 ++++++++++++++------- es-core/src/components/FlexboxComponent.h | 4 +- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index bce5eaeca..00c023b64 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -138,11 +138,11 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("itemMargin")) { const glm::vec2 itemMargin = elem->get("itemMargin"); - if (itemMargin.x < 0.0f || itemMargin.x > 100.0f || itemMargin.y < 0.0f || - itemMargin.y > 100.0f) { + if (itemMargin.x < 0.0f || itemMargin.x > 0.2f || itemMargin.y < 0.0f || + itemMargin.y > 0.2f) { LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" - << itemMargin.x << "x" << itemMargin.y << "\""; + << itemMargin.x << " " << itemMargin.y << "\""; } else { mFlexboxComponent.setItemMargin(itemMargin); diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 4d921e47f..f9b81abd9 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -11,8 +11,8 @@ #define DEFAULT_ITEMS_PER_LINE 4 #define DEFAULT_LINES 2 #define DEFAULT_ITEM_PLACEMENT "center" -#define DEFAULT_MARGIN_X 10.0f -#define DEFAULT_MARGIN_Y 10.0f +#define DEFAULT_MARGIN_X std::roundf(0.01f * Renderer::getScreenWidth()) +#define DEFAULT_MARGIN_Y std::roundf(0.01f * Renderer::getScreenHeight()) #include "components/FlexboxComponent.h" @@ -33,6 +33,32 @@ FlexboxComponent::FlexboxComponent(Window* window, { } +void FlexboxComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + if (!mLayoutValid) + computeLayout(); + + glm::mat4 trans{parentTrans * getTransform()}; + Renderer::setMatrix(trans); + + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); + + for (auto& image : mImages) { + if (mOpacity == 255) { + image.second.render(trans); + } + else { + image.second.setOpacity(mOpacity); + image.second.render(trans); + image.second.setOpacity(255); + } + } +} + void FlexboxComponent::computeLayout() { // Start placing items in the top-left. @@ -49,19 +75,35 @@ void FlexboxComponent::computeLayout() directionRow = {1, 0}; } + // If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering + // issues could occur. + mItemMargin.x = glm::clamp(mItemMargin.x, 0.0f, mSize.x / 2.0f); + mItemMargin.y = glm::clamp(mItemMargin.y, 0.0f, mSize.y / 2.0f); + + // Also keep the size within reason. + mSize.x = glm::clamp(mSize.x, static_cast(Renderer::getScreenWidth()) * 0.03f, + static_cast(Renderer::getScreenWidth())); + mSize.y = glm::clamp(mSize.y, static_cast(Renderer::getScreenHeight()) * 0.03f, + static_cast(Renderer::getScreenHeight())); + // Compute maximum image dimensions. glm::vec2 grid; if (mDirection == "row") grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; + glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; + maxItemSize.x = floorf(maxItemSize.x); + maxItemSize.y = floorf(maxItemSize.y); if (grid.x * grid.y < static_cast(mImages.size())) { LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges " "exceeds the product of times "; } + glm::vec2 sizeChange{0.0f, 0.0f}; + // Set final image dimensions. for (auto& image : mImages) { if (!image.second.isVisible()) @@ -78,9 +120,22 @@ void FlexboxComponent::computeLayout() newSize = sizeMaxX; else newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; - image.second.setResize(newSize.x, newSize.y); + + if (image.second.getSize() != newSize) + image.second.setResize(newSize.x, newSize.y); + + // In case maxItemSize needs to be updated. + if (newSize.x != sizeChange.x) + sizeChange.x = newSize.x; + if (newSize.y != sizeChange.y) + sizeChange.y = newSize.y; } + if (maxItemSize.x != sizeChange.x) + maxItemSize.x = sizeChange.x; + if (maxItemSize.y != sizeChange.y) + maxItemSize.y = sizeChange.y; + // Pre-compute layout parameters. float anchorXStart{anchorX}; float anchorYStart{anchorY}; @@ -140,29 +195,3 @@ void FlexboxComponent::computeLayout() mLayoutValid = true; } - -void FlexboxComponent::render(const glm::mat4& parentTrans) -{ - if (!isVisible()) - return; - - if (!mLayoutValid) - computeLayout(); - - glm::mat4 trans{parentTrans * getTransform()}; - Renderer::setMatrix(trans); - - if (Settings::getInstance()->getBool("DebugImage")) - Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); - - for (auto& image : mImages) { - if (mOpacity == 255) { - image.second.render(trans); - } - else { - image.second.setOpacity(mOpacity); - image.second.render(trans); - image.second.setOpacity(255); - } - } -} diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index bbe385aba..41c24433e 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -15,10 +15,10 @@ class FlexboxComponent : public GuiComponent { public: - explicit FlexboxComponent(Window* window, - std::vector>& images); + FlexboxComponent(Window* window, std::vector>& images); // Getters/setters for the layout. + std::string getDirection() const { return mDirection; } void setDirection(const std::string& direction) { assert(direction == "row" || direction == "column"); From d0ccc8a13d42c7ab4e241a040497271bbc72425b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 18:47:34 +0200 Subject: [PATCH 11/11] Documentation update. --- CHANGELOG.md | 1 + THEMES-DEV.md | 50 +++++++++++++++++++++--------------------------- USERGUIDE-DEV.md | 8 +++++--- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7e306b4..a7a83df73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button * Added key repeat for the "Jump to" and "Sort games by" selectors on the game options menu * Added key repeat when editing the "Release date" entry in the metadata editor (DateTimeEditComponent) +* Added support for setting the Kidgame metadata flag for folders (which will only affect the badges) * Achieved a massive speed improvement for OptionListComponent by not resizing each added MenuComponent row (most notable in the filter GUI) * Made multiple optimizations to the GUI components by removing lots of unnecessary function calls for sizing, placement, opacity changes etc. * Simplified the logic for info popups and prepared the code for the future "multiple popups" feature diff --git a/THEMES-DEV.md b/THEMES-DEV.md index b2a659cf0..e13aea2c4 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -633,7 +633,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the image will be rotated. Defaults to `0.5 0.5`. + - Point around which the image will be rotated. Default is `0.5 0.5`. * `path` - type: PATH. - Path to the image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). * `default` - type: PATH. @@ -707,7 +707,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `delay` - type: FLOAT. Default is false. - Delay in seconds before video will start playing. * `default` - type: PATH. @@ -736,7 +736,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `text` - type: STRING. * `color` - type: COLOR. * `backgroundColor` - type: COLOR; @@ -808,7 +808,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `rotation` - type: FLOAT. - angle in degrees that the rating should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the rating will be rotated. Defaults to `0.5 0.5`. + - Point around which the rating will be rotated. Default is `0.5 0.5`. * `filledPath` - type: PATH. - Path to the "filled star" image. Image must be square (width equals height). * `unfilledPath` - type: PATH. @@ -832,7 +832,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `color` - type: COLOR. * `backgroundColor` - type: COLOR; * `fontPath` - type: PATH. @@ -913,41 +913,35 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. - Possible combinations: - - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. + - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. Minimum value per axis is `0.03`, maximum value is `1.0`. Default is `0.15 0.20`. * `origin` - type: NORMALIZED_PAIR. - - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. + - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. Default is `0 0`. * `rotation` - type: FLOAT. - - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. + - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the image will be rotated. Defaults to `0.5 0.5`. + - Point around which the image will be rotated. Default is `0.5 0.5`. * `itemsPerRow` - type: FLOAT. - - Number of badges that fit on a row. When more badges are available a new row will be started. + - Number of badges that fit on a row. When more badges are available a new row will be started. Default is `4`. * `rows` - type: FLOAT. - - The number of rows available. + - The number of rows available. Default is `2`. * `itemPlacement` - type: STRING. - - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. + - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. Default is `center`. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - - `x y` - horizontal and vertical margins. + - `x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01`. * `slots` - type: STRING. - - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. - - Available badges are: - - "favorite": Will be shown when the game is marked as favorite. - - "completed": Will be shown when the game is marked as completed. - - "kidgame": Will be shown when the game is marked as a kids game. - - "broken": Will be shown when the game is marked as broken. - - "altemulator": Will be shown when an alternative emulator is setup for the game. + - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. Available badges are: + - `favorite`: Will be shown when the game is marked as favorite. + - `completed`: Will be shown when the game is marked as completed. + - `kidgame`: Will be shown when the game is marked as a kids game. + - `broken`: Will be shown when the game is marked as broken. + - `altemulator`: Will be shown when an alternative emulator is setup for the game. * `customBadgeIcon` - type: PATH. - - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are: - `favorite`, - `completed`, - `kidgame`, - `broken`, - `altemulator` + - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above. * `visible` - type: BOOLEAN. - If true, component will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. * `zIndex` - type: FLOAT. - - z-index value for component. Components will be rendered in order of z-index value from low to high. + - z-index value for component. Components will be rendered in order of z-index value from low to high. Default is `50`. #### carousel @@ -971,7 +965,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren - Default is 7.5 - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". * `logoRotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the logos will be rotated. Defaults to `-5 0.5`. + - Point around which the logos will be rotated. Default is `-5 0.5`. - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". * `logoAlignment` - type: STRING. - Sets the alignment of the logos relative to the carousel. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 34d760fd6..c0117dc04 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -213,6 +213,8 @@ In addition to the styles just described, there is a **Grid** view style as well If the theme supports it, there's a gamelist information field displayed in the gamelist view, showing the number of games for the system (total and favorites) as well as a folder icon if a folder has been entered. When applying any filters to the gamelist, the game counter is replaced with the amount of games filtered, as in 'filtered / total games', e.g. '19 / 77'. If there are game entries in the filter result that are marked not to be counted as games, the number of such files will be indicated as 'filtered + filtered non-games / total games', for example '23 + 4 / 77' indicating 23 normal games, 4 non-games out of a total of 77. Due to this approach it's theoretically possible that the combined filtered game amount exceeds the number of counted games in the collection, for instance '69 + 11 / 77'. This is not considered a bug and is so by design. This gamelist information field functionality is specific to EmulationStation Desktop Edition so older themes will not support this. +Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. + ![alt text](images/es-de_gamelist_view.png "ES-DE Gamelist View") _The **Gamelist view** is where you browse the games for a specific system._ @@ -1444,9 +1446,9 @@ A flag to indicate whether this is a favorite game. This flag can also be set di A flag to indicate whether you have completed the game. -**Kidgame** _(files only)_ +**Kidgame** -A flag to mark whether the game is suitable for children. This will be applied as a filter when starting ES-DE in _Kid_ mode. +A flag to mark whether the game is suitable for children. This will be applied as a filter when starting ES-DE in _Kid_ mode. Although it's possible to also set this flag for folders, this will **not** affect the actual files inside those folders. It will instead only be used to display the Kidgame badge for the folders themselves. **Hidden** @@ -1466,7 +1468,7 @@ Whether to exclude the file from the multi-scraper. This is quite useful in orde **Hide metadata fields** -This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. +This option will hide most metadata fields as well as any badges. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. **Times played** _(files only)_