// SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition // TextComponent.cpp // // Displays text. // #include "components/TextComponent.h" #include "utils/StringUtil.h" #include "Log.h" #include "Settings.h" TextComponent::TextComponent( Window* window) : GuiComponent(window), mFont(Font::get(FONT_SIZE_MEDIUM)), mUppercase(false), mColor(0x000000FF), mAutoCalcExtent(true, true), mHorizontalAlignment(ALIGN_LEFT), mVerticalAlignment(ALIGN_CENTER), mLineSpacing(1.5f), mBgColor(0), mMargin(0.0f), mRenderBackground(false) { } TextComponent::TextComponent( Window* window, const std::string& text, const std::shared_ptr& font, unsigned int color, Alignment align, Vector3f pos, Vector2f size, unsigned int bgcolor, float margin) : GuiComponent(window), mFont(nullptr), mUppercase(false), mColor(0x000000FF), mAutoCalcExtent(true, true), mHorizontalAlignment(align), mVerticalAlignment(ALIGN_CENTER), mLineSpacing(1.5f), mBgColor(0), mMargin(margin), mRenderBackground(false) { setFont(font); setColor(color); setBackgroundColor(bgcolor); setText(text); setPosition(pos); setSize(size); } void TextComponent::onSizeChanged() { mAutoCalcExtent = Vector2i((getSize().x() == 0), (getSize().y() == 0)); onTextChanged(); } void TextComponent::setFont(const std::shared_ptr& font) { mFont = font; onTextChanged(); } // Set the color of the font/text. void TextComponent::setColor(unsigned int color) { mColor = color; mColorOpacity = mColor & 0x000000FF; onColorChanged(); } // Set the color of the background box. void TextComponent::setBackgroundColor(unsigned int color) { mBgColor = color; mBgColorOpacity = mBgColor & 0x000000FF; } void TextComponent::setRenderBackground(bool render) { mRenderBackground = render; } // Scale the opacity. void TextComponent::setOpacity(unsigned char opacity) { // This function is mostly called to do fading in-out of the Text component element. // Therefore, we assume here that opacity is a fractional value (expressed as an int 0-255), // of the opacity originally set with setColor() or setBackgroundColor(). unsigned char o = static_cast(static_cast(opacity) / 255.f * static_cast(mColorOpacity)); mColor = (mColor & 0xFFFFFF00) | static_cast(o); unsigned char bgo = static_cast(static_cast(opacity) / 255.f * static_cast(mBgColorOpacity)); mBgColor = (mBgColor & 0xFFFFFF00) | static_cast(bgo); onColorChanged(); GuiComponent::setOpacity(opacity); } unsigned char TextComponent::getOpacity() const { return mColor & 0x000000FF; } void TextComponent::setText(const std::string& text) { mText = text; onTextChanged(); } void TextComponent::setHiddenText(const std::string& text) { mHiddenText = text; } void TextComponent::setUppercase(bool uppercase) { mUppercase = uppercase; onTextChanged(); } void TextComponent::render(const Transform4x4f& parentTrans) { if (!isVisible()) return; Transform4x4f trans = parentTrans * getTransform(); if (mRenderBackground) { Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), mBgColor, mBgColor); } if (mTextCache) { const Vector2f& textSize = mTextCache->metrics.size; float yOff = 0; switch (mVerticalAlignment) { case ALIGN_TOP: yOff = 0; break; case ALIGN_BOTTOM: yOff = (getSize().y() - textSize.y()); break; case ALIGN_CENTER: yOff = (getSize().y() - textSize.y()) / 2.0f; break; default: break; } Vector3f off(0, yOff, 0); if (Settings::getInstance()->getBool("DebugText")) { // Draw the "textbox" area, what we are aligned within. Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0xFF000033, 0xFF000033); } trans.translate(off); Renderer::setMatrix(trans); // Draw the text area, where the text actually is located. if (Settings::getInstance()->getBool("DebugText")) { switch (mHorizontalAlignment) { case ALIGN_LEFT: Renderer::drawRect(0.0f, 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033); break; case ALIGN_CENTER: Renderer::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: Renderer::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()); } } void TextComponent::calculateExtent() { if (mAutoCalcExtent.x()) { mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing); } else { if (mAutoCalcExtent.y()) mSize[1] = mFont->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText, getSize().x(), mLineSpacing).y(); } } void TextComponent::onTextChanged() { calculateExtent(); if (!mFont || mText.empty()) { mTextCache.reset(); return; } std::string text = mUppercase ? Utils::String::toUpper(mText) : mText; std::shared_ptr f = mFont; const bool isMultiline = (mSize.y() == 0 || mSize.y() > f->getHeight()*1.2f); bool addAbbrev = false; if (!isMultiline) { size_t newline = text.find('\n'); // Single line of text - stop at the first newline since it'll mess everything up. text = text.substr(0, newline); addAbbrev = newline != std::string::npos; } Vector2f size = f->sizeText(text); if (!isMultiline && mSize.x() && text.size() && (size.x() > mSize.x() || addAbbrev)) { // Abbreviate text. const std::string abbrev = "..."; Vector2f abbrevSize = f->sizeText(abbrev); // mMargin adds a margin around the text if it's abbreviated. float marginAdjustedSize = mSize.x() - (mSize.x() * mMargin); while (text.size() && size.x() + abbrevSize.x() > marginAdjustedSize) { size_t newSize = Utils::String::prevCursor(text, text.size()); text.erase(newSize, text.size() - newSize); size = f->sizeText(text); } text.append(abbrev); mTextCache = std::shared_ptr(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing)); } else { mTextCache = std::shared_ptr(f->buildTextCache(f->wrapText( text, mSize.x()), Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing)); } } void TextComponent::onColorChanged() { if (mTextCache) mTextCache->setColor(mColor); } void TextComponent::setHorizontalAlignment(Alignment align) { mHorizontalAlignment = align; onTextChanged(); } void TextComponent::setVerticalAlignment(Alignment align) { mVerticalAlignment = align; } void TextComponent::setLineSpacing(float spacing) { mLineSpacing = spacing; onTextChanged(); } std::string TextComponent::getValue() const { return mText; } void TextComponent::setValue(const std::string& value) { setText(value); } std::string TextComponent::getHiddenValue() const { return mHiddenText; } void TextComponent::setHiddenValue(const std::string& value) { setHiddenText(value); } void TextComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) { GuiComponent::applyTheme(theme, view, element, properties); using namespace ThemeFlags; const ThemeData::ThemeElement* elem = theme->getElement(view, element, "text"); if (!elem) return; if (properties & COLOR && elem->has("color")) setColor(elem->get("color")); setRenderBackground(false); if (properties & COLOR && elem->has("backgroundColor")) { setBackgroundColor(elem->get("backgroundColor")); setRenderBackground(true); } if (properties & ALIGNMENT && elem->has("alignment")) { std::string str = elem->get("alignment"); if (str == "left") setHorizontalAlignment(ALIGN_LEFT); else if (str == "center") setHorizontalAlignment(ALIGN_CENTER); else if (str == "right") setHorizontalAlignment(ALIGN_RIGHT); else LOG(LogError) << "Unknown text alignment string: " << str; } if (properties & TEXT && elem->has("text")) setText(elem->get("text")); if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); if (properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(elem->get("lineSpacing")); setFont(Font::getFromTheme(elem, properties, mFont)); }