mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-18 15:15:37 +00:00
Initial implementation, including flexbox layout for badges.
This commit is contained in:
parent
6b727e3883
commit
fe413bb68f
|
@ -32,6 +32,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root)
|
||||||
, mLblPlayers(window)
|
, mLblPlayers(window)
|
||||||
, mLblLastPlayed(window)
|
, mLblLastPlayed(window)
|
||||||
, mLblPlayCount(window)
|
, mLblPlayCount(window)
|
||||||
|
, mBadges(window)
|
||||||
, mRating(window)
|
, mRating(window)
|
||||||
, mReleaseDate(window)
|
, mReleaseDate(window)
|
||||||
, mDeveloper(window)
|
, mDeveloper(window)
|
||||||
|
@ -75,6 +76,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root)
|
||||||
addChild(&mImage);
|
addChild(&mImage);
|
||||||
|
|
||||||
// Metadata labels + values.
|
// Metadata labels + values.
|
||||||
|
addChild(&mBadges);
|
||||||
mLblRating.setText("Rating: ");
|
mLblRating.setText("Rating: ");
|
||||||
addChild(&mLblRating);
|
addChild(&mLblRating);
|
||||||
addChild(&mRating);
|
addChild(&mRating);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H
|
#ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H
|
||||||
#define 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/DateTimeComponent.h"
|
||||||
#include "components/RatingComponent.h"
|
#include "components/RatingComponent.h"
|
||||||
#include "components/ScrollableContainer.h"
|
#include "components/ScrollableContainer.h"
|
||||||
|
@ -46,6 +47,7 @@ private:
|
||||||
TextComponent mLblLastPlayed;
|
TextComponent mLblLastPlayed;
|
||||||
TextComponent mLblPlayCount;
|
TextComponent mLblPlayCount;
|
||||||
|
|
||||||
|
BadgesComponent mBadges;
|
||||||
RatingComponent mRating;
|
RatingComponent mRating;
|
||||||
DateTimeComponent mReleaseDate;
|
DateTimeComponent mReleaseDate;
|
||||||
TextComponent mDeveloper;
|
TextComponent mDeveloper;
|
||||||
|
|
|
@ -35,6 +35,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root)
|
||||||
, mLblPlayers(window)
|
, mLblPlayers(window)
|
||||||
, mLblLastPlayed(window)
|
, mLblLastPlayed(window)
|
||||||
, mLblPlayCount(window)
|
, mLblPlayCount(window)
|
||||||
|
, mBadges(window)
|
||||||
, mRating(window)
|
, mRating(window)
|
||||||
, mReleaseDate(window)
|
, mReleaseDate(window)
|
||||||
, mDeveloper(window)
|
, mDeveloper(window)
|
||||||
|
@ -55,6 +56,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root)
|
||||||
populateList(root->getChildrenListToDisplay(), root);
|
populateList(root->getChildrenListToDisplay(), root);
|
||||||
|
|
||||||
// Metadata labels + values.
|
// Metadata labels + values.
|
||||||
|
addChild(&mBadges);
|
||||||
mLblRating.setText("Rating: ");
|
mLblRating.setText("Rating: ");
|
||||||
addChild(&mLblRating);
|
addChild(&mLblRating);
|
||||||
addChild(&mRating);
|
addChild(&mRating);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H
|
#ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H
|
||||||
#define 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/DateTimeComponent.h"
|
||||||
#include "components/ImageGridComponent.h"
|
#include "components/ImageGridComponent.h"
|
||||||
#include "components/RatingComponent.h"
|
#include "components/RatingComponent.h"
|
||||||
|
@ -85,6 +86,7 @@ private:
|
||||||
TextComponent mLblLastPlayed;
|
TextComponent mLblLastPlayed;
|
||||||
TextComponent mLblPlayCount;
|
TextComponent mLblPlayCount;
|
||||||
|
|
||||||
|
BadgesComponent mBadges;
|
||||||
ImageComponent mMarquee;
|
ImageComponent mMarquee;
|
||||||
ImageComponent mImage;
|
ImageComponent mImage;
|
||||||
RatingComponent mRating;
|
RatingComponent mRating;
|
||||||
|
|
|
@ -46,6 +46,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root)
|
||||||
, mPublisher(window)
|
, mPublisher(window)
|
||||||
, mGenre(window)
|
, mGenre(window)
|
||||||
, mPlayers(window)
|
, mPlayers(window)
|
||||||
|
, mBadges(window)
|
||||||
, mLastPlayed(window)
|
, mLastPlayed(window)
|
||||||
, mPlayCount(window)
|
, mPlayCount(window)
|
||||||
, mName(window)
|
, mName(window)
|
||||||
|
@ -110,6 +111,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root)
|
||||||
mLblPlayers.setText("Players: ");
|
mLblPlayers.setText("Players: ");
|
||||||
addChild(&mLblPlayers);
|
addChild(&mLblPlayers);
|
||||||
addChild(&mPlayers);
|
addChild(&mPlayers);
|
||||||
|
addChild(&mBadges);
|
||||||
mLblLastPlayed.setText("Last played: ");
|
mLblLastPlayed.setText("Last played: ");
|
||||||
addChild(&mLblLastPlayed);
|
addChild(&mLblLastPlayed);
|
||||||
mLastPlayed.setDisplayRelative(true);
|
mLastPlayed.setDisplayRelative(true);
|
||||||
|
@ -162,6 +164,8 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
mVideo->applyTheme(theme, getName(), "md_video",
|
mVideo->applyTheme(theme, getName(), "md_video",
|
||||||
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION |
|
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION |
|
||||||
VISIBLE);
|
VISIBLE);
|
||||||
|
mBadges.applyTheme(theme, getName(), "md_badges",
|
||||||
|
POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE);
|
||||||
mName.applyTheme(theme, getName(), "md_name", ALL);
|
mName.applyTheme(theme, getName(), "md_name", ALL);
|
||||||
|
|
||||||
initMDLabels();
|
initMDLabels();
|
||||||
|
@ -176,10 +180,10 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
|
|
||||||
initMDValues();
|
initMDValues();
|
||||||
std::vector<GuiComponent*> values = getMDValues();
|
std::vector<GuiComponent*> values = getMDValues();
|
||||||
assert(values.size() == 8);
|
assert(values.size() == 9);
|
||||||
std::vector<std::string> valElements = {"md_rating", "md_releasedate", "md_developer",
|
std::vector<std::string> valElements = {"md_rating", "md_releasedate", "md_developer",
|
||||||
"md_publisher", "md_genre", "md_players",
|
"md_publisher", "md_genre", "md_players",
|
||||||
"md_lastplayed", "md_playcount"};
|
"md_badges", "md_lastplayed", "md_playcount"};
|
||||||
|
|
||||||
for (unsigned int i = 0; i < values.size(); i++)
|
for (unsigned int i = 0; i < values.size(); i++)
|
||||||
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
|
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
|
||||||
|
@ -243,6 +247,10 @@ void VideoGameListView::initMDValues()
|
||||||
mPublisher.setFont(defaultFont);
|
mPublisher.setFont(defaultFont);
|
||||||
mGenre.setFont(defaultFont);
|
mGenre.setFont(defaultFont);
|
||||||
mPlayers.setFont(defaultFont);
|
mPlayers.setFont(defaultFont);
|
||||||
|
|
||||||
|
// TODO: Set appropriate default height.
|
||||||
|
mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast<float>(defaultFont->getHeight()));
|
||||||
|
|
||||||
mLastPlayed.setFont(defaultFont);
|
mLastPlayed.setFont(defaultFont);
|
||||||
mPlayCount.setFont(defaultFont);
|
mPlayCount.setFont(defaultFont);
|
||||||
|
|
||||||
|
@ -315,6 +323,7 @@ void VideoGameListView::updateInfoPanel()
|
||||||
mGenre.setVisible(false);
|
mGenre.setVisible(false);
|
||||||
mLblPlayers.setVisible(false);
|
mLblPlayers.setVisible(false);
|
||||||
mPlayers.setVisible(false);
|
mPlayers.setVisible(false);
|
||||||
|
mBadges.setVisible(false);
|
||||||
mLblLastPlayed.setVisible(false);
|
mLblLastPlayed.setVisible(false);
|
||||||
mLastPlayed.setVisible(false);
|
mLastPlayed.setVisible(false);
|
||||||
mLblPlayCount.setVisible(false);
|
mLblPlayCount.setVisible(false);
|
||||||
|
@ -333,6 +342,7 @@ void VideoGameListView::updateInfoPanel()
|
||||||
mGenre.setVisible(true);
|
mGenre.setVisible(true);
|
||||||
mLblPlayers.setVisible(true);
|
mLblPlayers.setVisible(true);
|
||||||
mPlayers.setVisible(true);
|
mPlayers.setVisible(true);
|
||||||
|
mBadges.setVisible(true);
|
||||||
mLblLastPlayed.setVisible(true);
|
mLblLastPlayed.setVisible(true);
|
||||||
mLastPlayed.setVisible(true);
|
mLastPlayed.setVisible(true);
|
||||||
mLblPlayCount.setVisible(true);
|
mLblPlayCount.setVisible(true);
|
||||||
|
@ -437,6 +447,18 @@ void VideoGameListView::updateInfoPanel()
|
||||||
mPublisher.setValue(file->metadata.get("publisher"));
|
mPublisher.setValue(file->metadata.get("publisher"));
|
||||||
mGenre.setValue(file->metadata.get("genre"));
|
mGenre.setValue(file->metadata.get("genre"));
|
||||||
mPlayers.setValue(file->metadata.get("players"));
|
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"));
|
mName.setValue(file->metadata.get("name"));
|
||||||
|
|
||||||
if (file->getType() == GAME) {
|
if (file->getType() == GAME) {
|
||||||
|
@ -504,6 +526,7 @@ std::vector<GuiComponent*> VideoGameListView::getMDValues()
|
||||||
ret.push_back(&mPublisher);
|
ret.push_back(&mPublisher);
|
||||||
ret.push_back(&mGenre);
|
ret.push_back(&mGenre);
|
||||||
ret.push_back(&mPlayers);
|
ret.push_back(&mPlayers);
|
||||||
|
ret.push_back(&mBadges);
|
||||||
ret.push_back(&mLastPlayed);
|
ret.push_back(&mLastPlayed);
|
||||||
ret.push_back(&mPlayCount);
|
ret.push_back(&mPlayCount);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H
|
#ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H
|
||||||
#define 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/DateTimeComponent.h"
|
||||||
#include "components/RatingComponent.h"
|
#include "components/RatingComponent.h"
|
||||||
#include "components/ScrollableContainer.h"
|
#include "components/ScrollableContainer.h"
|
||||||
|
@ -50,6 +51,7 @@ private:
|
||||||
TextComponent mLblLastPlayed;
|
TextComponent mLblLastPlayed;
|
||||||
TextComponent mLblPlayCount;
|
TextComponent mLblPlayCount;
|
||||||
|
|
||||||
|
BadgesComponent mBadges;
|
||||||
RatingComponent mRating;
|
RatingComponent mRating;
|
||||||
DateTimeComponent mReleaseDate;
|
DateTimeComponent mReleaseDate;
|
||||||
TextComponent mDeveloper;
|
TextComponent mDeveloper;
|
||||||
|
|
|
@ -34,6 +34,7 @@ set(CORE_HEADERS
|
||||||
|
|
||||||
# GUI components
|
# GUI components
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h
|
${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/BusyComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h
|
||||||
|
@ -108,6 +109,7 @@ set(CORE_SOURCES
|
||||||
|
|
||||||
# GUI components
|
# GUI components
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp
|
${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/BusyComponent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp
|
||||||
|
|
|
@ -151,6 +151,10 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
|
||||||
{"size", NORMALIZED_PAIR},
|
{"size", NORMALIZED_PAIR},
|
||||||
{"origin", NORMALIZED_PAIR},
|
{"origin", NORMALIZED_PAIR},
|
||||||
{"direction", STRING},
|
{"direction", STRING},
|
||||||
|
{"wrap", STRING},
|
||||||
|
{"justifyContent", STRING},
|
||||||
|
{"align", STRING},
|
||||||
|
{"margin", NORMALIZED_PAIR},
|
||||||
{"slots", STRING},
|
{"slots", STRING},
|
||||||
{"customBadgeIcon", PATH},
|
{"customBadgeIcon", PATH},
|
||||||
{"visible", BOOLEAN},
|
{"visible", BOOLEAN},
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace ThemeFlags
|
||||||
Z_INDEX = 8192,
|
Z_INDEX = 8192,
|
||||||
ROTATION = 16384,
|
ROTATION = 16384,
|
||||||
VISIBLE = 32768,
|
VISIBLE = 32768,
|
||||||
|
DIRECTION = 65536,
|
||||||
ALL = 0xFFFFFFFF
|
ALL = 0xFFFFFFFF
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
381
es-core/src/components/BadgesComponent.cpp
Normal file
381
es-core/src/components/BadgesComponent.cpp
Normal file
|
@ -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 <numeric>
|
||||||
|
|
||||||
|
#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<std::string>();
|
||||||
|
mSlots.push_back(SLOT_FAVORITE);
|
||||||
|
mSlots.push_back(SLOT_COMPLETED);
|
||||||
|
mSlots.push_back(SLOT_KIDS);
|
||||||
|
mSlots.push_back(SLOT_BROKEN);
|
||||||
|
|
||||||
|
mBadgeIcons = std::map<std::string, std::string>();
|
||||||
|
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<std::string, std::shared_ptr<TextureResource>>();
|
||||||
|
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<std::string, Renderer::Vertex[4]>();
|
||||||
|
|
||||||
|
// 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<size_t>(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<float> 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<float> widths;
|
||||||
|
std::vector<float> 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<ThemeData>& 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<std::string>(slot);
|
||||||
|
mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true);
|
||||||
|
imgChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties & DIRECTION && elem->has("direction"))
|
||||||
|
mDirection = elem->get<std::string>("direction");
|
||||||
|
|
||||||
|
if (elem->has("wrap"))
|
||||||
|
mWrap = elem->get<std::string>("wrap");
|
||||||
|
|
||||||
|
if (elem->has("justifyContent"))
|
||||||
|
mJustifyContent = elem->get<std::string>("justifyContent");
|
||||||
|
|
||||||
|
if (elem->has("align"))
|
||||||
|
mAlign = elem->get<std::string>("align");
|
||||||
|
|
||||||
|
if (elem->has("slots"))
|
||||||
|
setValue(elem->get<std::string>("slots"));
|
||||||
|
|
||||||
|
GuiComponent::applyTheme(theme, view, element, properties);
|
||||||
|
|
||||||
|
if (imgChanged)
|
||||||
|
onSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelpPrompt> BadgesComponent::getHelpPrompts()
|
||||||
|
{
|
||||||
|
std::vector<HelpPrompt> prompts;
|
||||||
|
return prompts;
|
||||||
|
}
|
88
es-core/src/components/BadgesComponent.h
Normal file
88
es-core/src/components/BadgesComponent.h
Normal file
|
@ -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::string>);
|
||||||
|
|
||||||
|
std::vector<std::string> getSlots();
|
||||||
|
|
||||||
|
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||||
|
const std::string& view,
|
||||||
|
const std::string& element,
|
||||||
|
unsigned int properties) override;
|
||||||
|
|
||||||
|
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateVertices();
|
||||||
|
std::map<std::string, Renderer::Vertex[4]> mVertices;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> mBadgeIcons;
|
||||||
|
std::map<std::string, std::shared_ptr<TextureResource>> mTextures;
|
||||||
|
|
||||||
|
std::string mDirection;
|
||||||
|
std::string mWrap;
|
||||||
|
std::string mJustifyContent;
|
||||||
|
std::string mAlign;
|
||||||
|
glm::vec2 mMargin;
|
||||||
|
std::vector<std::string> mSlots;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H
|
BIN
resources/graphics/badge_broken.png
Normal file
BIN
resources/graphics/badge_broken.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
resources/graphics/badge_completed.png
Normal file
BIN
resources/graphics/badge_completed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
resources/graphics/badge_favorite.png
Normal file
BIN
resources/graphics/badge_favorite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
resources/graphics/badge_kidgame.png
Normal file
BIN
resources/graphics/badge_kidgame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -241,11 +241,16 @@ based on: 'recalbox-multi' by the Recalbox community
|
||||||
<size>0.1 0.3</size>
|
<size>0.1 0.3</size>
|
||||||
<origin>0 0</origin>
|
<origin>0 0</origin>
|
||||||
<direction>row</direction>
|
<direction>row</direction>
|
||||||
<slots>favorite completed kids broken</slots>
|
<wrap>wrap</wrap> <!-- wrap | nowrap | wrap-reverse -->
|
||||||
<customBadgeIcon badge="favorite">:/graphics/star_filled.svg</customBadgeIcon>
|
<justifyContent>start
|
||||||
<customBadgeIcon badge="completed">:/graphics/star_filled.svg</customBadgeIcon>
|
</justifyContent> <!-- start | end | center | space-between | space-around | space-evenly -->
|
||||||
<customBadgeIcon badge="kids">:/graphics/star_filled.svg</customBadgeIcon>
|
<align>start</align> <!-- start | end | center | stretch -->
|
||||||
<customBadgeIcon badge="broken">:/graphics/star_filled.svg</customBadgeIcon>
|
<margin>20 10</margin>
|
||||||
|
<slots>favorite completed kidgame broken</slots>
|
||||||
|
<customBadgeIcon badge="favorite">:/graphics/badge_favorite.png</customBadgeIcon>
|
||||||
|
<customBadgeIcon badge="completed">:/graphics/badge_completed.png</customBadgeIcon>
|
||||||
|
<customBadgeIcon badge="kidgame">:/graphics/badge_kidgame.png</customBadgeIcon>
|
||||||
|
<customBadgeIcon badge="broken">:/graphics/badge_broken.png</customBadgeIcon>
|
||||||
</badges>
|
</badges>
|
||||||
<!-- This block prevents additional elements from interfering when mixing layouts. -->
|
<!-- This block prevents additional elements from interfering when mixing layouts. -->
|
||||||
<image name="backframe4" extra="false"></image>
|
<image name="backframe4" extra="false"></image>
|
||||||
|
|
Loading…
Reference in a new issue