mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-29 09:35:39 +00:00
Added a 'containerType' property to the text element to select between vertical and horizontal containers
This commit is contained in:
parent
23749f16eb
commit
e7ada6111b
|
@ -270,6 +270,9 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
|||
bool container {false};
|
||||
if (element.second.has("container")) {
|
||||
container = element.second.get<bool>("container");
|
||||
if (element.second.has("containerType") &&
|
||||
element.second.get<std::string>("containerType") == "horizontal")
|
||||
container = false;
|
||||
}
|
||||
else if (element.second.has("metadata") &&
|
||||
element.second.get<std::string>("metadata") == "description") {
|
||||
|
@ -794,6 +797,9 @@ void GamelistView::updateView(const CursorState& state)
|
|||
for (auto& container : mContainerComponents)
|
||||
container->reset();
|
||||
|
||||
for (auto& scrollableText : mTextComponents)
|
||||
scrollableText->resetLooping();
|
||||
|
||||
for (auto& rating : mRatingComponents)
|
||||
rating->setValue(file->metadata.get("rating"));
|
||||
|
||||
|
|
|
@ -69,6 +69,9 @@ void SystemView::goToSystem(SystemData* system, bool animate)
|
|||
selector->setNeedsRefresh();
|
||||
}
|
||||
|
||||
for (auto& text : mSystemElements[mPrimary->getCursor()].textComponents)
|
||||
text->resetLooping();
|
||||
|
||||
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents)
|
||||
video->setStaticVideo();
|
||||
|
||||
|
@ -136,6 +139,9 @@ void SystemView::update(int deltaTime)
|
|||
{
|
||||
mPrimary->update(deltaTime);
|
||||
|
||||
for (auto& text : mSystemElements[mPrimary->getCursor()].textComponents)
|
||||
text->update(deltaTime);
|
||||
|
||||
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents) {
|
||||
if (!isScrolling())
|
||||
video->update(deltaTime);
|
||||
|
@ -217,6 +223,9 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
|
|||
|
||||
void SystemView::onCursorChanged(const CursorState& state)
|
||||
{
|
||||
for (auto& text : mSystemElements[mPrimary->getCursor()].textComponents)
|
||||
text->resetLooping();
|
||||
|
||||
const int cursor {mPrimary->getCursor()};
|
||||
const int scrollVelocity {mPrimary->getScrollingVelocity()};
|
||||
const ViewTransitionAnimation transitionAnim {static_cast<ViewTransitionAnimation>(
|
||||
|
@ -591,6 +600,9 @@ void SystemView::populate()
|
|||
bool container {false};
|
||||
if (element.second.has("container")) {
|
||||
container = element.second.get<bool>("container");
|
||||
if (element.second.has("containerType") &&
|
||||
element.second.get<std::string>("containerType") == "horizontal")
|
||||
container = false;
|
||||
}
|
||||
else if (element.second.has("metadata") &&
|
||||
element.second.get<std::string>("metadata") == "description") {
|
||||
|
|
|
@ -379,6 +379,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
|
|||
{"gameselector", STRING},
|
||||
{"gameselectorEntry", UNSIGNED_INTEGER},
|
||||
{"container", BOOLEAN},
|
||||
{"containerType", STRING},
|
||||
{"containerVerticalSnap", BOOLEAN},
|
||||
{"containerScrollSpeed", FLOAT},
|
||||
{"containerStartDelay", FLOAT},
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "Window.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
||||
TextComponent::TextComponent()
|
||||
|
@ -32,6 +33,14 @@ TextComponent::TextComponent()
|
|||
, mNoTopMargin {false}
|
||||
, mSelectable {false}
|
||||
, mVerticalAutoSizing {false}
|
||||
, mLoopHorizontal {false}
|
||||
, mLoopScroll {false}
|
||||
, mLoopSpeed {0.0f}
|
||||
, mLoopSpeedMultiplier {1.0f}
|
||||
, mLoopDelay {1500.0f}
|
||||
, mLoopOffset1 {0}
|
||||
, mLoopOffset2 {0}
|
||||
, mLoopTime {0}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -62,6 +71,14 @@ TextComponent::TextComponent(const std::string& text,
|
|||
, mNoTopMargin {false}
|
||||
, mSelectable {false}
|
||||
, mVerticalAutoSizing {false}
|
||||
, mLoopHorizontal {false}
|
||||
, mLoopScroll {false}
|
||||
, mLoopSpeed {0.0f}
|
||||
, mLoopSpeedMultiplier {1.0f}
|
||||
, mLoopDelay {1500.0f}
|
||||
, mLoopOffset1 {0}
|
||||
, mLoopOffset2 {0}
|
||||
, mLoopTime {0}
|
||||
{
|
||||
setFont(font);
|
||||
setColor(color);
|
||||
|
@ -176,75 +193,124 @@ void TextComponent::render(const glm::mat4& parentTrans)
|
|||
glm::mat4 trans {parentTrans * getTransform()};
|
||||
mRenderer->setMatrix(trans);
|
||||
|
||||
if (mRenderBackground)
|
||||
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, mBgColor, mBgColor, false,
|
||||
mOpacity * mThemeOpacity, mDimming);
|
||||
|
||||
if (mTextCache) {
|
||||
const glm::vec2& textSize {mTextCache->metrics.size};
|
||||
float yOff {0.0f};
|
||||
|
||||
if (mSize.y > textSize.y) {
|
||||
switch (mVerticalAlignment) {
|
||||
case ALIGN_TOP: {
|
||||
yOff = 0.0f;
|
||||
break;
|
||||
}
|
||||
case ALIGN_BOTTOM: {
|
||||
yOff = mSize.y - textSize.y;
|
||||
break;
|
||||
}
|
||||
case ALIGN_CENTER: {
|
||||
yOff = (mSize.y - textSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If height is smaller than the font height, then always center vertically.
|
||||
yOff = (mSize.y - textSize.y) / 2.0f;
|
||||
}
|
||||
|
||||
// Draw the overall textbox area. If we're inside a scrollable container then this
|
||||
// area is rendered inside that component instead of here.
|
||||
if (Settings::getInstance()->getBool("DebugText")) {
|
||||
if (!mParent || !mParent->isScrollable())
|
||||
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0x0000FF33, 0x0000FF33);
|
||||
}
|
||||
|
||||
trans = glm::translate(trans, glm::vec3 {0.0f, yOff, 0.0f});
|
||||
mRenderer->setMatrix(trans);
|
||||
|
||||
// Draw the text area, where the text actually is located.
|
||||
if (Settings::getInstance()->getBool("DebugText")) {
|
||||
switch (mHorizontalAlignment) {
|
||||
case ALIGN_LEFT: {
|
||||
mRenderer->drawRect(0.0f, 0.0f, mTextCache->metrics.size.x,
|
||||
mTextCache->metrics.size.y, 0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
case ALIGN_CENTER: {
|
||||
mRenderer->drawRect((mSize.x - mTextCache->metrics.size.x) / 2.0f, 0.0f,
|
||||
mTextCache->metrics.size.x, mTextCache->metrics.size.y,
|
||||
0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
case ALIGN_RIGHT: {
|
||||
mRenderer->drawRect(mSize.x - mTextCache->metrics.size.x, 0.0f,
|
||||
mTextCache->metrics.size.x, mTextCache->metrics.size.y,
|
||||
0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mFont->renderTextCache(mTextCache.get());
|
||||
// Draw the overall textbox area. If we're inside a vertical scrollable container then
|
||||
// this area is rendered inside that component instead of here.
|
||||
if (Settings::getInstance()->getBool("DebugText")) {
|
||||
if (!mParent || !mParent->isScrollable())
|
||||
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0x0000FF33, 0x0000FF33);
|
||||
}
|
||||
|
||||
if (mLoopHorizontal && mTextCache != nullptr) {
|
||||
// Clip everything to be inside our bounds.
|
||||
glm::vec3 dim {mSize.x, mSize.y, 0.0f};
|
||||
dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x;
|
||||
dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y;
|
||||
|
||||
const int clipRectPosX {static_cast<int>(std::round(trans[3].x))};
|
||||
const int clipRectPosY {static_cast<int>(std::round(trans[3].y))};
|
||||
const int clipRectSizeX {static_cast<int>(std::round(dim.x))};
|
||||
const int clipRectSizeY {static_cast<int>(std::round(dim.y) + 1.0f)};
|
||||
|
||||
mRenderer->pushClipRect(glm::ivec2 {clipRectPosX, clipRectPosY},
|
||||
glm::ivec2 {clipRectSizeX, clipRectSizeY});
|
||||
|
||||
float offsetX {0.0f};
|
||||
|
||||
if (mTextCache->metrics.size.x < mSize.x) {
|
||||
if (mHorizontalAlignment == Alignment::ALIGN_CENTER)
|
||||
offsetX = static_cast<float>((mSize.x - mTextCache->metrics.size.x) / 2.0f);
|
||||
else if (mHorizontalAlignment == Alignment::ALIGN_RIGHT)
|
||||
offsetX = mSize.x - mTextCache->metrics.size.x;
|
||||
}
|
||||
|
||||
trans = glm::translate(trans,
|
||||
glm::vec3 {offsetX - static_cast<float>(mLoopOffset1), 0.0f, 0.0f});
|
||||
}
|
||||
|
||||
auto renderFunc = [this](glm::mat4 trans) {
|
||||
if (mRenderBackground)
|
||||
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, mBgColor, mBgColor, false,
|
||||
mOpacity * mThemeOpacity, mDimming);
|
||||
if (mTextCache) {
|
||||
const glm::vec2& textSize {mTextCache->metrics.size};
|
||||
float yOff {0.0f};
|
||||
|
||||
if (mSize.y > textSize.y) {
|
||||
switch (mVerticalAlignment) {
|
||||
case ALIGN_TOP: {
|
||||
yOff = 0.0f;
|
||||
break;
|
||||
}
|
||||
case ALIGN_BOTTOM: {
|
||||
yOff = mSize.y - textSize.y;
|
||||
break;
|
||||
}
|
||||
case ALIGN_CENTER: {
|
||||
yOff = (mSize.y - textSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If height is smaller than the font height, then always center vertically.
|
||||
yOff = (mSize.y - textSize.y) / 2.0f;
|
||||
}
|
||||
|
||||
trans = glm::translate(trans, glm::vec3 {0.0f, yOff, 0.0f});
|
||||
mRenderer->setMatrix(trans);
|
||||
|
||||
// Draw the text area, where the text is actually located.
|
||||
if (Settings::getInstance()->getBool("DebugText")) {
|
||||
switch (mHorizontalAlignment) {
|
||||
case ALIGN_LEFT: {
|
||||
mRenderer->drawRect(0.0f, 0.0f, mTextCache->metrics.size.x,
|
||||
mTextCache->metrics.size.y, 0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
case ALIGN_CENTER: {
|
||||
mRenderer->drawRect(
|
||||
mLoopHorizontal ? 0.0f : (mSize.x - mTextCache->metrics.size.x) / 2.0f,
|
||||
0.0f, mTextCache->metrics.size.x, mTextCache->metrics.size.y,
|
||||
0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
case ALIGN_RIGHT: {
|
||||
mRenderer->drawRect(mLoopHorizontal ? 0.0f :
|
||||
mSize.x - mTextCache->metrics.size.x,
|
||||
0.0f, mTextCache->metrics.size.x,
|
||||
mTextCache->metrics.size.y, 0x00000033, 0x00000033);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mFont->renderTextCache(mTextCache.get());
|
||||
}
|
||||
};
|
||||
|
||||
renderFunc(trans);
|
||||
|
||||
if (mLoopHorizontal && mTextCache != nullptr && mTextCache->metrics.size.x > mSize.x) {
|
||||
// Needed to avoid flickering when returning to the start position.
|
||||
if (mLoopOffset1 == 0 && mLoopOffset2 == 0)
|
||||
mLoopScroll = false;
|
||||
// Render again if text has moved far enough for it to repeat.
|
||||
if (mLoopOffset2 < 0 || (mLoopDelay != 0.0f && mLoopScroll)) {
|
||||
mLoopScroll = true;
|
||||
trans = glm::translate(parentTrans * getTransform(),
|
||||
glm::vec3 {static_cast<float>(-mLoopOffset2), 0.0f, 0.0f});
|
||||
mRenderer->setMatrix(trans);
|
||||
renderFunc(trans);
|
||||
}
|
||||
}
|
||||
|
||||
if (mLoopHorizontal && mTextCache != nullptr)
|
||||
mRenderer->popClipRect();
|
||||
}
|
||||
|
||||
void TextComponent::setValue(const std::string& value)
|
||||
|
@ -254,11 +320,65 @@ void TextComponent::setValue(const std::string& value)
|
|||
mThemeMetadata == "genre" || mThemeMetadata == "players")) {
|
||||
setText(mDefaultValue);
|
||||
}
|
||||
else if (mLoopHorizontal) {
|
||||
setText(Utils::String::replace(value, "\n", ""));
|
||||
}
|
||||
else {
|
||||
setText(value);
|
||||
}
|
||||
}
|
||||
|
||||
void TextComponent::setHorizontalLooping(bool state)
|
||||
{
|
||||
resetLooping();
|
||||
mLoopHorizontal = state;
|
||||
|
||||
if (mLoopHorizontal)
|
||||
mLoopSpeed =
|
||||
mFont->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f * mLoopSpeedMultiplier;
|
||||
}
|
||||
|
||||
void TextComponent::update(int deltaTime)
|
||||
{
|
||||
if (mLoopHorizontal && mTextCache != nullptr) {
|
||||
// Don't scroll if the media viewer or screensaver is active or if text scrolling
|
||||
// is disabled;
|
||||
if (mWindow->isMediaViewerActive() || mWindow->isScreensaverActive() ||
|
||||
!mWindow->getAllowTextScrolling()) {
|
||||
if (mLoopTime != 0 && !mWindow->isLaunchScreenDisplayed())
|
||||
resetLooping();
|
||||
return;
|
||||
}
|
||||
|
||||
assert(mLoopSpeed != 0.0f);
|
||||
|
||||
mLoopOffset1 = 0;
|
||||
mLoopOffset2 = 0;
|
||||
|
||||
if (mTextCache->metrics.size.x > mSize.x) {
|
||||
// Loop the text.
|
||||
const float scrollLength {mTextCache->metrics.size.x};
|
||||
const float returnLength {mLoopSpeed * 1.5f / mLoopSpeedMultiplier};
|
||||
const float scrollTime {(scrollLength * 1000.0f) / mLoopSpeed};
|
||||
const float returnTime {(returnLength * 1000.0f) / mLoopSpeed};
|
||||
const int maxTime {static_cast<int>(mLoopDelay + scrollTime + returnTime)};
|
||||
|
||||
mLoopTime += deltaTime;
|
||||
while (mLoopTime > maxTime)
|
||||
mLoopTime -= maxTime;
|
||||
|
||||
mLoopOffset1 = static_cast<int>(Utils::Math::loop(mLoopDelay, scrollTime + returnTime,
|
||||
static_cast<float>(mLoopTime),
|
||||
scrollLength + returnLength));
|
||||
|
||||
if (mLoopOffset1 > (scrollLength - (mSize.x - returnLength)))
|
||||
mLoopOffset2 = static_cast<int>(mLoopOffset1 - (scrollLength + returnLength));
|
||||
else if (mLoopOffset2 < 0)
|
||||
mLoopOffset2 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextComponent::onTextChanged()
|
||||
{
|
||||
mTextCache.reset();
|
||||
|
@ -298,7 +418,10 @@ void TextComponent::onTextChanged()
|
|||
|
||||
const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight};
|
||||
|
||||
if (isMultiline && !isScrollable) {
|
||||
if (mLoopHorizontal) {
|
||||
mTextCache = std::shared_ptr<TextCache>(font->buildTextCache(text, 0.0f, 0.0f, mColor));
|
||||
}
|
||||
else if (isMultiline && !isScrollable) {
|
||||
const std::string wrappedText {
|
||||
font->wrapText(text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight),
|
||||
mLineSpacing, isMultiline)};
|
||||
|
@ -436,6 +559,28 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|||
<< "\" defined as \"" << verticalAlignment << "\"";
|
||||
}
|
||||
|
||||
if (elem->has("container") && elem->get<bool>("container")) {
|
||||
if (elem->has("containerType")) {
|
||||
const std::string& containerType {elem->get<std::string>("containerType")};
|
||||
if (containerType == "horizontal") {
|
||||
if (elem->has("containerScrollSpeed")) {
|
||||
mLoopSpeedMultiplier =
|
||||
glm::clamp(elem->get<float>("containerScrollSpeed"), 0.1f, 10.0f);
|
||||
}
|
||||
if (elem->has("containerStartDelay")) {
|
||||
mLoopDelay =
|
||||
glm::clamp(elem->get<float>("containerStartDelay"), 0.0f, 10.0f) * 1000.0f;
|
||||
}
|
||||
mLoopHorizontal = true;
|
||||
}
|
||||
else if (containerType != "vertical") {
|
||||
LOG(LogError) << "TextComponent: Invalid theme configuration, property "
|
||||
"\"containerType\" for element \""
|
||||
<< element.substr(5) << "\" defined as \"" << containerType << "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (properties & TEXT && elem->has("text"))
|
||||
setText(elem->get<std::string>("text"));
|
||||
|
||||
|
@ -546,4 +691,8 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|||
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
|
||||
|
||||
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, false));
|
||||
|
||||
// We need to do this after setting the font as the loop speed is calculated from its size.
|
||||
if (mLoopHorizontal)
|
||||
setHorizontalLooping(true);
|
||||
}
|
||||
|
|
|
@ -86,6 +86,18 @@ public:
|
|||
return (mTextCache == nullptr ? 0 : mTextCache->metrics.maxGlyphHeight);
|
||||
}
|
||||
|
||||
// Horizontal looping for single-line content that is too long to fit.
|
||||
void setHorizontalLooping(bool state);
|
||||
|
||||
void resetLooping()
|
||||
{
|
||||
mLoopOffset1 = 0;
|
||||
mLoopOffset2 = 0;
|
||||
mLoopTime = 0;
|
||||
}
|
||||
|
||||
void update(int deltaTime) override;
|
||||
|
||||
protected:
|
||||
virtual void onTextChanged();
|
||||
|
||||
|
@ -131,6 +143,15 @@ private:
|
|||
bool mNoTopMargin;
|
||||
bool mSelectable;
|
||||
bool mVerticalAutoSizing;
|
||||
|
||||
bool mLoopHorizontal;
|
||||
bool mLoopScroll;
|
||||
float mLoopSpeed;
|
||||
float mLoopSpeedMultiplier;
|
||||
float mLoopDelay;
|
||||
int mLoopOffset1;
|
||||
int mLoopOffset2;
|
||||
int mLoopTime;
|
||||
};
|
||||
|
||||
#endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H
|
||||
|
|
|
@ -36,7 +36,7 @@ Font::Font(float size, const std::string& path, const bool linearMagnify)
|
|||
initLibrary();
|
||||
|
||||
// Always initialize ASCII characters.
|
||||
for (unsigned int i = 32; i < 127; ++i)
|
||||
for (unsigned int i {32}; i < 127; ++i)
|
||||
getGlyph(i);
|
||||
|
||||
clearFaceCache();
|
||||
|
@ -110,7 +110,7 @@ int Font::loadGlyphs(const std::string& text)
|
|||
{
|
||||
mMaxGlyphHeight = static_cast<int>(std::round(mFontSize));
|
||||
|
||||
for (size_t i = 0; i < text.length();) {
|
||||
for (size_t i {0}; i < text.length();) {
|
||||
unsigned int character {Utils::String::chars2Unicode(text, i)}; // Advances i.
|
||||
Glyph* glyph {getGlyph(character)};
|
||||
|
||||
|
@ -207,7 +207,7 @@ TextCache* Font::buildTextCache(const std::string& text,
|
|||
color};
|
||||
|
||||
// Round vertices.
|
||||
for (int i = 1; i < 5; ++i)
|
||||
for (int i {1}; i < 5; ++i)
|
||||
vertices[i].position = glm::round(vertices[i].position);
|
||||
|
||||
// Make duplicates of first and last vertex so this can be rendered as a triangle strip.
|
||||
|
@ -692,7 +692,7 @@ FT_Face Font::getFaceForChar(unsigned int id)
|
|||
static const std::vector<std::string> fallbackFonts {getFallbackFontPaths()};
|
||||
|
||||
// Look for the glyph in our current font and then in the fallback fonts if needed.
|
||||
for (unsigned int i = 0; i < fallbackFonts.size() + 1; ++i) {
|
||||
for (unsigned int i {0}; i < fallbackFonts.size() + 1; ++i) {
|
||||
auto fit = mFaceCache.find(i);
|
||||
|
||||
if (fit == mFaceCache.cend()) {
|
||||
|
|
Loading…
Reference in a new issue