Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions es-app/src/components/TextListComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class TextListComponent : public IList<TextListData, T>
inline void setAlignment(Alignment align) { mAlignment = align; }

inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; }
inline void setFavoriteIndicatorCallback(const std::function<bool(const T&)>& func) { mFavoriteIndicatorCallback = func; }

inline void setFont(const std::shared_ptr<Font>& font)
{
Expand Down Expand Up @@ -109,17 +110,20 @@ class TextListComponent : public IList<TextListData, T>
bool mSelectorColorGradientHorizontal = true;
unsigned int mSelectedColor;
std::string mScrollSound;
std::function<bool(const T&)> mFavoriteIndicatorCallback;
static const unsigned int COLOR_ID_COUNT = 2;
unsigned int mColors[COLOR_ID_COUNT];
int mViewportHeight;
int mCursorPrev = -1;

ImageComponent mSelectorImage;
ImageComponent mFavoriteIcon;
bool mFavoriteIconVisible;
};

template <typename T>
TextListComponent<T>::TextListComponent(Window* window) :
IList<TextListData, T>(window), mSelectorImage(window)
IList<TextListData, T>(window), mSelectorImage(window), mFavoriteIcon(window)
{
mMarqueeOffset = 0;
mMarqueeOffset2 = 0;
Expand All @@ -139,6 +143,9 @@ TextListComponent<T>::TextListComponent(Window* window) :
mSelectedColor = 0;
mColors[0] = 0x0000FFFF;
mColors[1] = 0x00FF00FF;

mFavoriteIcon.setImage(":/heart_filled.svg");
mFavoriteIconVisible = true;
}

template <typename T>
Expand Down Expand Up @@ -204,6 +211,12 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)

entry.data.textCache->setColor(color);

const bool showFavorite = mFavoriteIconVisible && (mFavoriteIndicatorCallback ? mFavoriteIndicatorCallback(entry.object) : false);
const float iconSize = entrySize * 0.60f;
const float iconGap = showFavorite ? (iconSize * 0.35f) : 0.0f;
const float textWidth = entry.data.textCache->metrics.size.x();
const float lineWidth = showFavorite ? (iconSize + iconGap + textWidth) : textWidth;

Vector3f offset(0, y, 0);

switch(mAlignment)
Expand All @@ -212,26 +225,41 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
offset[0] = mHorizontalMargin;
break;
case ALIGN_CENTER:
offset[0] = (int)((mSize.x() - entry.data.textCache->metrics.size.x()) / 2);
offset[0] = (int)((mSize.x() - lineWidth) / 2);
if(offset[0] < mHorizontalMargin)
offset[0] = mHorizontalMargin;
break;
case ALIGN_RIGHT:
offset[0] = (mSize.x() - entry.data.textCache->metrics.size.x());
offset[0] = (mSize.x() - lineWidth);
offset[0] -= mHorizontalMargin;
if(offset[0] < mHorizontalMargin)
offset[0] = mHorizontalMargin;
break;
}

float rowScrollOffset = 0.0f;
if((mCursor == i) && (mMarqueeOffset > 0))
rowScrollOffset = (float)mMarqueeOffset;

if(showFavorite)
{
Transform4x4f iconTrans = trans;
iconTrans.translate(offset - Vector3f(rowScrollOffset, 0, 0));
mFavoriteIcon.setPosition(0, (entrySize - iconSize) * 0.5f, 0);
mFavoriteIcon.setResize(iconSize, iconSize);
mFavoriteIcon.render(iconTrans);
}

Vector3f textOffset = offset + Vector3f(showFavorite ? (iconSize + iconGap) : 0.0f, 0, 0);

// render text
Transform4x4f drawTrans = trans;

// currently selected item text might be scrolling
if((mCursor == i) && (mMarqueeOffset > 0))
drawTrans.translate(offset - Vector3f((float)mMarqueeOffset, 0, 0));
drawTrans.translate(textOffset - Vector3f((float)mMarqueeOffset, 0, 0));
else
drawTrans.translate(offset);
drawTrans.translate(textOffset);

Renderer::setMatrix(drawTrans);
font->renderTextCache(entry.data.textCache.get());
Expand All @@ -240,8 +268,16 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
// marquee is scrolled far enough for it to repeat
if((mCursor == i) && (mMarqueeOffset2 < 0))
{
// also render the favorite icon alongside the wrap-around copy of the text
if(showFavorite)
{
Transform4x4f iconTrans2 = trans;
iconTrans2.translate(offset - Vector3f((float)mMarqueeOffset2, 0, 0));
mFavoriteIcon.render(iconTrans2);
}

drawTrans = trans;
drawTrans.translate(offset - Vector3f((float)mMarqueeOffset2, 0, 0));
drawTrans.translate(textOffset - Vector3f((float)mMarqueeOffset2, 0, 0));
Renderer::setMatrix(drawTrans);
font->renderTextCache(entry.data.textCache.get());
}
Expand Down Expand Up @@ -481,6 +517,21 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, c
} else {
mSelectorImage.setImage("");
}

if (elem->has("favoriteIconPath"))
mFavoriteIcon.setImage(elem->get<std::string>("favoriteIconPath"));
else
mFavoriteIcon.setImage(":/heart_filled.svg");

if (elem->has("favoriteIconColor"))
mFavoriteIcon.setColorShift(elem->get<unsigned int>("favoriteIconColor"));
else
mFavoriteIcon.setColorShift(0xFFFFFFFF);

{
std::string var = theme->getVariable("favoriteIconVisible");
mFavoriteIconVisible = (var == "true");
}
}

#endif // ES_APP_COMPONENTS_TEXT_LIST_COMPONENT_H
3 changes: 3 additions & 0 deletions es-app/src/views/gamelist/BasicGameListView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ BasicGameListView::BasicGameListView(Window* window, FileData* root)
mList.setSize(mSize.x(), mSize.y() * 0.8f);
mList.setPosition(0, mSize.y() * 0.2f);
mList.setDefaultZIndex(20);
mList.setFavoriteIndicatorCallback([](FileData* file) {
return file != nullptr && file->getType() == GAME && file->metadata.get("favorite") == "true";
});
addChild(&mList);

populateList(root->getChildrenListToDisplay());
Expand Down
14 changes: 13 additions & 1 deletion es-core/src/ThemeData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
{ "horizontalMargin", RESOLUTION_FLOAT },
{ "forceUppercase", BOOLEAN },
{ "lineSpacing", FLOAT },
{ "favoriteIconPath", PATH },
{ "favoriteIconColor", COLOR },
{ "zIndex", FLOAT } } },
{ "container", {
{ "pos", RESOLUTION_PAIR },
Expand Down Expand Up @@ -515,7 +517,9 @@ void ThemeData::parseElement(const pugi::xml_node& root, const std::map<std::str
break;
case PATH:
{
std::string path = Utils::FileSystem::resolveRelativePath(str, mPaths.back(), true, false);
std::string path = (str.size() >= 2 && str[0] == ':' && str[1] == '/')
? str
: Utils::FileSystem::resolveRelativePath(str, mPaths.back(), true, false);
if(!ResourceManager::getInstance()->fileExists(path))
{
std::stringstream ss;
Expand Down Expand Up @@ -560,6 +564,14 @@ bool ThemeData::hasView(const std::string& view)
return (viewIt != mViews.cend());
}

std::string ThemeData::getVariable(const std::string& name) const
{
auto it = mVariables.find(name);
if (it != mVariables.cend())
return it->second;
return "";
}

const ThemeData::ThemeElement* ThemeData::getElement(const std::string& view, const std::string& element, const std::string& expectedType) const
{
auto viewIt = mViews.find(view);
Expand Down
3 changes: 3 additions & 0 deletions es-core/src/ThemeData.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ class ThemeData
// If expectedType is an empty string, will do no type checking.
const ThemeElement* getElement(const std::string& view, const std::string& element, const std::string& expectedType) const;

// Returns the value of a theme variable, or empty string if not defined.
std::string getVariable(const std::string& name) const;

static std::vector<GuiComponent*> makeExtras(const std::shared_ptr<ThemeData>& theme, const std::string& view, Window* window);

static const std::shared_ptr<ThemeData>& getDefault();
Expand Down
8 changes: 8 additions & 0 deletions resources/heart_filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.