diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 00bb64fed..6d1c5f13d 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -32,6 +32,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) + , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -75,6 +76,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mImage); // Metadata labels + values. + addChild(&mBadges); mLblRating.setText("Rating: "); addChild(&mLblRating); addChild(&mRating); diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index f0b0202a9..356d86179 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -46,6 +47,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index ae7eae6ee..3db148ade 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -35,6 +35,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) + , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -55,6 +56,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) populateList(root->getChildrenListToDisplay(), root); // Metadata labels + values. + addChild(&mBadges); mLblRating.setText("Rating: "); addChild(&mLblRating); addChild(&mRating); diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index f7a46ea6a..1f9b648f1 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/ImageGridComponent.h" #include "components/RatingComponent.h" @@ -85,6 +86,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; ImageComponent mMarquee; ImageComponent mImage; RatingComponent mRating; diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 669a3f868..2d2f4faa6 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -46,6 +46,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) , mPublisher(window) , mGenre(window) , mPlayers(window) + , mBadges(window) , mLastPlayed(window) , mPlayCount(window) , mName(window) @@ -110,6 +111,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); + addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -162,6 +164,8 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); + mBadges.applyTheme(theme, getName(), "md_badges", + POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); @@ -176,10 +180,10 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 8); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_lastplayed", "md_playcount"}; + 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"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -243,6 +247,10 @@ void VideoGameListView::initMDValues() mPublisher.setFont(defaultFont); mGenre.setFont(defaultFont); mPlayers.setFont(defaultFont); + + // TODO: Set appropriate default height. + mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); + mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); @@ -315,6 +323,7 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); + mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); @@ -333,6 +342,7 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); + mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); @@ -437,6 +447,18 @@ void VideoGameListView::updateInfoPanel() mPublisher.setValue(file->metadata.get("publisher")); mGenre.setValue(file->metadata.get("genre")); mPlayers.setValue(file->metadata.get("players")); + + // Generate badges slots value based on the game metadata. + std::stringstream ss; + ss << (file->metadata.get("favorite").compare("true") ? "favorite " : ""); + ss << (file->metadata.get("completed").compare("true") ? "completed " : ""); + ss << (file->metadata.get("kidgame").compare("true") ? "kidgame " : ""); + ss << (file->metadata.get("broken").compare("true") ? "broken " : ""); + std::string slots = ss.str(); + if (!slots.empty()) + slots.pop_back(); + mBadges.setValue(slots); + mName.setValue(file->metadata.get("name")); if (file->getType() == GAME) { @@ -504,6 +526,7 @@ 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 4f6988d1f..19c585188 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -50,6 +51,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index fb1959b8a..7dcbe1721 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -34,6 +34,7 @@ set(CORE_HEADERS # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h @@ -108,6 +109,7 @@ set(CORE_SOURCES # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 13ec5fc4c..37c3fae73 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -151,6 +151,10 @@ std::map> The {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"direction", STRING}, + {"wrap", STRING}, + {"justifyContent", STRING}, + {"align", STRING}, + {"margin", NORMALIZED_PAIR}, {"slots", STRING}, {"customBadgeIcon", PATH}, {"visible", BOOLEAN}, diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 181c215a2..9370a10c6 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -53,6 +53,7 @@ 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 new file mode 100644 index 000000000..a8ac67636 --- /dev/null +++ b/es-core/src/components/BadgesComponent.cpp @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// BadgesComponent.cpp +// +// Game badges icons. +// Used by gamelist views. +// + +#include "components/BadgesComponent.h" +#include + +#include "Settings.h" +#include "ThemeData.h" +#include "resources/TextureResource.h" + +BadgesComponent::BadgesComponent(Window* window) + : GuiComponent(window) + , mDirection(DEFAULT_DIRECTION) + , mWrap(DEFAULT_WRAP) + , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) + , mAlign(DEFAULT_ALIGN) +{ + mSlots = std::vector(); + mSlots.push_back(SLOT_FAVORITE); + mSlots.push_back(SLOT_COMPLETED); + mSlots.push_back(SLOT_KIDS); + mSlots.push_back(SLOT_BROKEN); + + mBadgeIcons = std::map(); + mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; + mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.png"; + mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png"; + mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png"; + + mTextures = std::map>(); + mTextures[SLOT_FAVORITE] = TextureResource::get(mBadgeIcons[SLOT_FAVORITE], true); + mTextures[SLOT_COMPLETED] = TextureResource::get(mBadgeIcons[SLOT_COMPLETED], true); + mTextures[SLOT_KIDS] = TextureResource::get(mBadgeIcons[SLOT_KIDS], true); + mTextures[SLOT_BROKEN] = TextureResource::get(mBadgeIcons[SLOT_BROKEN], true); + + mVertices = std::map(); + + // TODO: Should be dependent on the direction property. + mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f}; + + // TODO: Add definition for default value. + mMargin = glm::vec2{10.0f, 10.0f}; + + updateVertices(); +} + +void BadgesComponent::setValue(const std::string& value) +{ + if (value.empty()) { + mSlots.clear(); + } + else { + // Start by clearing the slots. + mSlots.clear(); + + // Interpret the value and iteratively fill mSlots. The value is a space separated list of + // strings. + std::string temp; + std::istringstream ss(value); + while (std::getline(ss, temp, ' ')) { + if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || + temp == SLOT_BROKEN)) + LOG(LogError) << "Badge slot '" << temp << "' is invalid."; + else + mSlots.push_back(temp); + } + } + + updateVertices(); +} + +std::string BadgesComponent::getValue() const +{ + std::stringstream ss; + for (auto& slot : mSlots) + ss << slot << ' '; + std::string r = ss.str(); + r.pop_back(); + return r; +} + +void BadgesComponent::onSizeChanged() +{ + // TODO: Should be dependent on the direction property. + if (mSize.y == 0.0f) + mSize.y = mSize.x / NUM_SLOTS; + else if (mSize.x == 0.0f) + mSize.x = mSize.y * NUM_SLOTS; + + if (mSize.y > 0.0f) { + size_t heightPx = static_cast(std::round(mSize.y)); + for (auto const& tex : mTextures) + tex.second->rasterizeAt(heightPx, heightPx); + } + + updateVertices(); +} + +void BadgesComponent::updateVertices() +{ + mVertices.clear(); + + /*const float numSlots = mSlots.size(); + float s; + if (mDirection == DIRECTION_ROW) + s = std::min( getSize().x / numSlots, getSize().y ); + else + s = std::min( getSize().y / numSlots, getSize().x ); + const long color = 4278190080; + + int i = 0; + for (auto & slot : mSlots) + { + // clang-format off + mVertices[slot][0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color}; + mVertices[slot][1] = {{0.0f, s}, {0.0f, 0.0f}, color}; + mVertices[slot][2] = {{s , 0.0f}, {1.0f, 1.0f}, color}; + mVertices[slot][3] = {{s , s}, {1.0f, 0.0f}, color}; + // clang-format on + i++; + }*/ + + // The maximum number of badges to be displayed. + const float numSlots = NUM_SLOTS; + + // The available size to draw in. + const auto size = getSize(); + + // Compute the number of rows and columns and the item max dimensions. + int rows; + int columns; + float itemWidth; + float itemHeight; + + if (mDirection == DIRECTION_ROW) { + if (mWrap != WRAP_NOWRAP) { + // Suppose we have i rows, what would be the average area of an icon? Compute for a + // small number of rows. + std::vector areas; + for (int i = 1; i < 10; i++) { + + float area = size.x * size.y; + + // Number of vertical gaps. + int verticalGaps = i - 1; + + // Area of vertical gaps. + area -= verticalGaps * mMargin.y * size.x; + + // Height per item. + float iHeight = (size.y - verticalGaps * mMargin.y) / i; + + // Width per item. (Approximation) + // TODO: this is an approximation! + // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight = + // 0; + float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x; + + // Average area available per badge + float avgArea = iHeight * iWidth; + + // Push to the areas array. + areas.push_back(avgArea); + } + + // Determine the number of rows based on what results in the largest area per badge + // based on available space. + rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1; + + // Obtain final item dimensions. + itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; + itemWidth = areas[rows - 1] / itemHeight; + + // Compute number of columns. + if (rows == 1) + columns = NUM_SLOTS; + else + columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); + } + else { + rows = 1; + columns = NUM_SLOTS; + itemHeight = size.y; + itemWidth = size.x / (NUM_SLOTS + (NUM_SLOTS - 1) * mMargin.x); + } + } + else { + // TODO: Add computation for column direction. + } + + const long color = 4278190080; + if (mDirection == DIRECTION_ROW) { + + // Start row. + int row = mWrap == WRAP_REVERSE ? rows : 1; + int item = 0; + + // Iterate through all the rows. + for (int c = 0; c < rows && item < mSlots.size(); c++) { + + // Pre-compute dimensions of all items in this row. + std::vector widths; + std::vector heights; + int itemTemp = item; + for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) { + glm::vec texSize = mTextures[mSlots[itemTemp]]->getSize(); + float aspectRatioTexture = texSize.x / texSize.y; + float aspectRatioItemSpace = itemWidth / itemHeight; + if (aspectRatioTexture > aspectRatioItemSpace) { + widths.push_back(itemWidth); + heights.push_back(itemWidth / aspectRatioTexture); + } + else { + widths.push_back(itemHeight * aspectRatioTexture); + heights.push_back(itemHeight); + } + itemTemp++; + } + + // Iterate through the columns. + float xpos = 0; + for (int column = 0; column < columns && item < mSlots.size(); column++) { + + // We always go from left to right. + // Here we compute the coordinates of the items. + + // Compute final badge x position. + float x; + float totalWidth = + std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) + + (widths.size() - 1) * mMargin.x; + if (mJustifyContent == "start") { + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "end") { + if (column == 0) + xpos += size.x - totalWidth; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "center") { + if (column == 0) + xpos += (size.x - totalWidth) / 2; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "space-between") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + x = xpos; + xpos += widths[column] + gapSize; + } + else if (mJustifyContent == "space-around") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + xpos += gapSize / 2; + x = xpos; + xpos += widths[column] + gapSize / 2; + } + else if (mJustifyContent == "space-evenly") { + float gapSize = (size.x - totalWidth) / (widths.size() + 1); + xpos += gapSize; + x = xpos; + } + + // Compute final badge y position. + float y = row * itemHeight; + if (mAlign == "end") { + y += itemHeight - heights[column]; + } + else if (mAlign == "center") { + y += (itemHeight - heights[column]) / 2; + } + if (mAlign == "stretch") { + heights[column] = itemHeight; + } + + LOG(LogError) << "Computed Final Item Position. Row: " << row + << ", Column: " << column << ", Item: " << item << ", pos: (" << x + << ", " << y << "), size: (" << widths[column] << ", " + << heights[column] << ")"; + + // Store the item's vertices and apply texture mapping. + // clang-format off + mVertices[mSlots[item]][0] = {{x, y}, {0.0f, 1.0f}, color}; + mVertices[mSlots[item]][1] = {{x, y+heights[column]}, {0.0f, 0.0f}, color}; + mVertices[mSlots[item]][2] = {{x+widths[column] , y}, {1.0f, 1.0f}, color}; + mVertices[mSlots[item]][3] = {{x+widths[column] , y+heights[column]}, {1.0f, 0.0f}, color}; + // clang-format on + + // Increment item; + item++; + } + + // Iterate the row. + mWrap == WRAP_REVERSE ? row-- : row++; + } + } +} + +void BadgesComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + glm::mat4 trans{parentTrans * getTransform()}; + + Renderer::setMatrix(trans); + + if (mOpacity > 0) { + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); + + for (auto& slot : mSlots) { + if (mTextures[slot] == nullptr) + continue; + + if (mTextures[slot]->bind()) { + Renderer::drawTriangleStrips(mVertices[slot], 4); + Renderer::bindTexture(0); + } + + // TODO: update render matrix to position of next slot + // trans = glm::translate(trans, {0.0f, 0.0f, 1.0f}); + } + } + + renderChildren(trans); +} + +void BadgesComponent::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) +{ + using namespace ThemeFlags; + + const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); + if (!elem) + return; + + bool imgChanged = false; + for (auto& slot : mSlots) { + if (properties & PATH && elem->has(slot)) { + mBadgeIcons[slot] = elem->get(slot); + mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true); + imgChanged = true; + } + } + + if (properties & DIRECTION && elem->has("direction")) + mDirection = elem->get("direction"); + + if (elem->has("wrap")) + mWrap = elem->get("wrap"); + + if (elem->has("justifyContent")) + mJustifyContent = elem->get("justifyContent"); + + if (elem->has("align")) + mAlign = elem->get("align"); + + if (elem->has("slots")) + setValue(elem->get("slots")); + + GuiComponent::applyTheme(theme, view, element, properties); + + if (imgChanged) + onSizeChanged(); +} + +std::vector BadgesComponent::getHelpPrompts() +{ + std::vector prompts; + return prompts; +} diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h new file mode 100644 index 000000000..906463cca --- /dev/null +++ b/es-core/src/components/BadgesComponent.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// BadgesComponent.h +// +// Game badges icons. +// Used by gamelist views. +// + +#ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H +#define ES_APP_COMPONENTS_BADGES_COMPONENT_H + +#include "GuiComponent.h" +#include "renderers/Renderer.h" + +#define DIRECTION_ROW "row" +#define DIRECTION_COLUMN "column" +#define WRAP_WRAP "wrap" +#define WRAP_NOWRAP "nowrap" +#define WRAP_REVERSE "wrap-reverse" +#define JUSTIFY_CONTENT_START "start" +#define JUSTIFY_CONTENT_END "end" +#define JUSTIFY_CONTENT_CENTER "center" +#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between" +#define JUSTIFY_CONTENT_SPACE_AROUND "space-around" +#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly" +#define ITEM_ALIGN_START "start" +#define ITEM_ALIGN_END "end" +#define ITEM_ALIGN_CENTER "center" +#define ITEM_ALIGN_STRETCH "stretch" +#define NUM_SLOTS 4 +#define SLOT_FAVORITE "favorite" +#define SLOT_COMPLETED "completed" +#define SLOT_KIDS "kidgame" +#define SLOT_BROKEN "broken" +#define DEFAULT_DIRECTION DIRECTION_ROW +#define DEFAULT_WRAP WRAP_WRAP +#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START +#define DEFAULT_ALIGN ITEM_ALIGN_CENTER +#define DEFAULT_MARGIN_X = 10.0f +#define DEFAULT_MARGIN_Y = 10.0f + +class TextureResource; + +class BadgesComponent : public GuiComponent +{ +public: + BadgesComponent(Window* window); + + std::string getValue() const override; + // Should be a list of strings. + void setValue(const std::string& value) override; + + void render(const glm::mat4& parentTrans) override; + + void onSizeChanged() override; + + void setDirection(int direction); + + int getDirection(); + + void setSlots(std::vector); + + std::vector getSlots(); + + virtual void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + + virtual std::vector getHelpPrompts() override; + +private: + void updateVertices(); + std::map mVertices; + + std::map mBadgeIcons; + std::map> mTextures; + + std::string mDirection; + std::string mWrap; + std::string mJustifyContent; + std::string mAlign; + glm::vec2 mMargin; + std::vector mSlots; +}; + +#endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H diff --git a/resources/graphics/badge_broken.png b/resources/graphics/badge_broken.png new file mode 100644 index 000000000..ae97d4c8b Binary files /dev/null and b/resources/graphics/badge_broken.png differ diff --git a/resources/graphics/badge_completed.png b/resources/graphics/badge_completed.png new file mode 100644 index 000000000..3a5c1369a Binary files /dev/null and b/resources/graphics/badge_completed.png differ diff --git a/resources/graphics/badge_favorite.png b/resources/graphics/badge_favorite.png new file mode 100644 index 000000000..3cb8be1ac Binary files /dev/null and b/resources/graphics/badge_favorite.png differ diff --git a/resources/graphics/badge_kidgame.png b/resources/graphics/badge_kidgame.png new file mode 100644 index 000000000..ccb13b9dd Binary files /dev/null and b/resources/graphics/badge_kidgame.png differ diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 6e26b63b5..dd6027f9a 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -241,11 +241,16 @@ based on: 'recalbox-multi' by the Recalbox community 0.1 0.3 0 0 row - favorite completed kids broken - :/graphics/star_filled.svg - :/graphics/star_filled.svg - :/graphics/star_filled.svg - :/graphics/star_filled.svg + wrap + start + + start + 20 10 + favorite completed kidgame broken + :/graphics/badge_favorite.png + :/graphics/badge_completed.png + :/graphics/badge_kidgame.png + :/graphics/badge_broken.png