Fix background color in Text (should render over text and not the bounding rectangle.

Having fixed this added background color to UIRichText and UITextSpan.
This commit is contained in:
Martín Lucas Golini
2026-03-05 21:27:35 -03:00
parent 99f4711408
commit 6a9c2bbeeb
13 changed files with 193 additions and 21 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -29,13 +29,15 @@ class FontStyleConfig {
const Vector2f& getFontShadowOffset() const { return ShadowOffset; }
const Color& getBackgroundColor() const { return BackgroundColor; }
FontStyleConfig() {}
bool operator==( const FontStyleConfig& other ) {
return Font == other.Font && CharacterSize == other.CharacterSize && Style == other.Style &&
FontColor == other.FontColor && ShadowColor == other.ShadowColor &&
ShadowOffset == other.ShadowOffset && OutlineThickness == other.OutlineThickness &&
OutlineColor == other.OutlineColor;
OutlineColor == other.OutlineColor && BackgroundColor == other.BackgroundColor;
}
bool operator!=( const FontStyleConfig& other ) { return !( *this == other ); }
@@ -49,6 +51,7 @@ class FontStyleConfig {
ShadowOffset = fontStyleConfig.ShadowOffset;
OutlineThickness = fontStyleConfig.OutlineThickness;
OutlineColor = fontStyleConfig.OutlineColor;
BackgroundColor = fontStyleConfig.BackgroundColor;
}
Graphics::Font* Font{ nullptr };
@@ -59,6 +62,7 @@ class FontStyleConfig {
Vector2f ShadowOffset{ PixelDensity::dpToPx( 1 ), PixelDensity::dpToPx( 1 ) };
Float OutlineThickness{ 0 };
Color OutlineColor{ 0, 0, 0, 255 };
Color BackgroundColor{ Color::Transparent };
};
}} // namespace EE::Graphics

View File

@@ -41,7 +41,8 @@ class EE_API RichText : public Drawable {
* @param style The text style (optional, uses default if Regular).
*/
void addSpan( const String& text, Font* font = nullptr, Uint32 characterSize = 0,
Color color = Color::White, Uint32 style = Text::Regular );
Color color = Color::White, Uint32 style = Text::Regular,
Color backgroundColor = Color::Transparent );
/** @brief Clears all text spans. */
void clear();

View File

@@ -400,7 +400,6 @@ class EE_API Text {
String mString; ///< String to display
FontStyleConfig mFontStyleConfig;
Color mBackgroundColor{ Color::Transparent };
mutable Rectf mBounds; ///< Bounding rectangle of the text (in local coordinates)
mutable bool mGeometryNeedUpdate : 1 { false }; ///< Does the geometry need to be recomputed?

View File

@@ -63,6 +63,10 @@ class EE_API UIRichText : public UILayout {
UIRichText* setFontColor( const Color& color );
const Color& getFontBackgroundColor() const;
UIRichText* setFontBackgroundColor( const Color& color );
const Color& getFontShadowColor() const;
UIRichText* setFontShadowColor( const Color& color );

View File

@@ -77,6 +77,10 @@ class EE_API UITextSpan : public UIWidget {
UITextSpan* setFontColor( const Color& color );
const Color& getFontBackgroundColor() const;
UITextSpan* setFontBackgroundColor( const Color& color );
const Color& getFontShadowColor() const;
UITextSpan* setFontShadowColor( const Color& color );
@@ -97,6 +101,7 @@ class EE_API UITextSpan : public UIWidget {
StyleStateOutlineColor = 1 << 5,
StyleStateFontShadowColor = 1 << 6,
StyleStateFontShadowOffset = 1 << 7,
StyleStateFontBackgroundColor = 1 << 8,
StyleStateAll = 0xFFFFFFFF
};
@@ -108,6 +113,7 @@ class EE_API UITextSpan : public UIWidget {
bool hasOutlineColor() const;
bool hasFontShadowColor() const;
bool hasFontShadowOffset() const;
bool hasFontBackgroundColor() const;
protected:
Uint32 mStyleState{ StyleStateNone };

View File

@@ -168,18 +168,20 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
? font->getGlyph( curChar, characterSize, bold, italic, outlineThickness ).advance
: hspace;
Float kerning = 0;
if ( curChar == '\t' ) {
w = Text::tabAdvance( hspace, tabWidth, tabStops ? xoffset : std::optional<Float>{} );
prevChar = 0;
} else if ( !isMonospace && curChar != '\r' ) {
if ( !( textDrawHints & TextHints::NoKerning ) ) {
w += font->getKerning( prevChar, curChar, characterSize, bold, italic,
outlineThickness );
kerning = font->getKerning( prevChar, curChar, characterSize, bold, italic,
outlineThickness );
}
prevChar = curChar;
}
xoffset += w;
Float xpos = xoffset + kerning;
xoffset = xpos + w;
if ( hasWrap && xoffset > maxWidth ) {
if ( mode == LineWrapMode::Word && lastSpace != std::string::npos ) {
@@ -188,7 +190,15 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
}
info.wraps.push_back( lastSpace + 1 );
xoffset = info.paddingStart + ( xoffset - lastWidth );
Float startKerning = 0;
if ( !( textDrawHints & TextHints::NoKerning ) && !isMonospace &&
lastSpace + 1 < string.size() ) {
startKerning = font->getKerning( string[lastSpace], string[lastSpace + 1],
characterSize, bold, italic, outlineThickness );
}
xoffset = info.paddingStart + ( xoffset - lastWidth - startKerning );
} else {
// If we are about to split a word, check if we can move it to the next line
if ( mode == LineWrapMode::Word && info.wraps.size() == 1 &&
@@ -213,6 +223,7 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
info.wraps.push_back( idx );
xoffset = info.paddingStart;
prevChar = 0;
}
}
lastSpace = std::string::npos;

View File

@@ -98,7 +98,7 @@ void RichText::addCustomSize( const Sizef& size ) {
}
void RichText::addSpan( const String& text, Font* font, Uint32 characterSize, Color color,
Uint32 style ) {
Uint32 style, Color backgroundColor ) {
FontStyleConfig config;
config.Font = font ? font : mDefaultStyle.Font;
config.CharacterSize = characterSize != 0 ? characterSize : mDefaultStyle.CharacterSize;
@@ -108,6 +108,7 @@ void RichText::addSpan( const String& text, Font* font, Uint32 characterSize, Co
config.ShadowOffset = mDefaultStyle.ShadowOffset;
config.OutlineThickness = mDefaultStyle.OutlineThickness;
config.OutlineColor = mDefaultStyle.OutlineColor;
config.BackgroundColor = backgroundColor;
addSpan( text, config );
}

View File

@@ -1575,11 +1575,11 @@ const Uint32& Text::getTabWidth() const {
}
Color Text::getBackgroundColor() const {
return mBackgroundColor;
return mFontStyleConfig.BackgroundColor;
}
void Text::setBackgroundColor( const Color& backgroundColor ) {
mBackgroundColor = backgroundColor;
mFontStyleConfig.BackgroundColor = backgroundColor;
}
const Vector2f& Text::getShadowOffset() const {
@@ -1629,11 +1629,11 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
BlendMode effect, const OriginPoint& rotationCenter,
const OriginPoint& scaleCenter, const std::vector<Color>& colors,
const std::vector<Color>& outlineColors, const Color& backgroundColor ) {
unsigned int numvert = mVertices.size();
if ( 0 == numvert )
if ( NULL == mFontStyleConfig.Font || mString.empty() )
return;
unsigned int numvert = mVertices.size();
GlobalBatchRenderer::instance()->draw();
if ( rotation != 0.0f || scale != 1.0f ) {
@@ -1670,7 +1670,32 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
Primitives p;
p.setForceDraw( true );
p.setColor( backgroundColor );
p.drawRectangle( getLocalBounds() );
ensureVisualLinesUpdate();
Float vspace = getLineSpacing();
for ( size_t i = 0; i < mVisualLines.size(); ++i ) {
Float centerDiffX = 0;
if ( i < mLinesWidth.size() ) {
switch ( Font::getHorizontalAlign( mAlign ) ) {
case TEXT_ALIGN_CENTER:
centerDiffX = std::trunc( ( mCachedWidth - mLinesWidth[i] ) * 0.5f );
break;
case TEXT_ALIGN_RIGHT:
centerDiffX = mCachedWidth - mLinesWidth[i];
break;
}
}
p.drawRectangle( Rectf( centerDiffX, i * vspace, centerDiffX + mLinesWidth[i],
( i + 1 ) * vspace ) );
}
}
if ( 0 == numvert ) {
if ( rotation != 0.0f || scale != 1.0f ) {
GLi->popMatrix();
} else {
GLi->translatef( -X, -Y, 0 );
}
return;
}
Texture* texture = mFontStyleConfig.Font->getTexture( mFontStyleConfig.CharacterSize );
@@ -1744,7 +1769,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
}
draw( X, Y, scale, rotation, effect, rotationCenter, scaleCenter, mColors, mOutlineColors,
mBackgroundColor );
mFontStyleConfig.BackgroundColor );
}
void Text::ensureGeometryUpdate() {
@@ -2330,6 +2355,7 @@ void Text::setStyleConfig( const FontStyleConfig& styleConfig ) {
setOutlineColor( styleConfig.OutlineColor );
setShadowColor( styleConfig.ShadowColor );
setShadowOffset( styleConfig.ShadowOffset );
setBackgroundColor( styleConfig.BackgroundColor );
}
bool Text::hasSameFontStyleConfig( const FontStyleConfig& styleConfig ) {

View File

@@ -620,11 +620,18 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result,
breakStringIdx = string.size();
ShapedGlyph& breakGlyph = sp.shapedGlyphs[breakIndex];
Float breakPos = breakGlyph.position.x;
Float kerning = 0;
if ( breakIndex > 0 ) {
ShapedGlyph& prevBreakGlyph = sp.shapedGlyphs[breakIndex - 1];
kerning = ( prevBreakGlyph.position.x + prevBreakGlyph.advance.x ) -
breakGlyph.position.x;
}
sp.wrapInfo.wraps.push_back( breakStringIdx );
sp.wrapInfo.wrapsWidth.push_back( std::ceil( breakGlyph.position.x ) );
sp.wrapInfo.wrapsWidth.push_back( std::ceil( breakPos + kerning ) );
Vector2f adjustment( -breakGlyph.position.x + sp.wrapInfo.paddingStart, vspace );
Vector2f adjustment( sp.wrapInfo.paddingStart - breakGlyph.position.x, vspace );
for ( std::size_t k = breakIndex; k <= idx; ++k )
sp.shapedGlyphs[k].position += adjustment;

View File

@@ -98,6 +98,9 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::Color:
setFontColor( attribute.asColor() );
break;
case PropertyId::BackgroundColor:
setFontBackgroundColor( attribute.asColor() );
break;
case PropertyId::TextShadowColor:
setFontShadowColor( attribute.asColor() );
break;
@@ -141,6 +144,8 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
return Graphics::Text::styleFlagToString( getFontStyle() );
case PropertyId::Color:
return getFontColor().toHexString();
case PropertyId::BackgroundColor:
return getFontBackgroundColor().toHexString();
case PropertyId::TextShadowColor:
return getFontShadowColor().toHexString();
case PropertyId::TextShadowOffset:
@@ -161,10 +166,10 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
std::vector<PropertyId> UIRichText::getPropertiesImplemented() const {
auto props = UILayout::getPropertiesImplemented();
auto local = {
PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle,
PropertyId::Color, PropertyId::TextShadowColor, PropertyId::TextShadowOffset,
PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, PropertyId::TextAlign };
auto local = { PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle,
PropertyId::Color, PropertyId::BackgroundColor, PropertyId::TextShadowColor,
PropertyId::TextShadowOffset, PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor,
PropertyId::TextAlign };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -229,6 +234,19 @@ UIRichText* UIRichText::setFontColor( const Color& color ) {
return this;
}
const Color& UIRichText::getFontBackgroundColor() const {
return mRichText.getFontStyleConfig().BackgroundColor;
}
UIRichText* UIRichText::setFontBackgroundColor( const Color& color ) {
if ( mRichText.getFontStyleConfig().BackgroundColor != color ) {
mRichText.getFontStyleConfig().BackgroundColor = color;
mRichText.invalidate();
updateDefaultSpansStyle();
}
return this;
}
const Color& UIRichText::getFontShadowColor() const {
return mRichText.getFontStyleConfig().ShadowColor;
}

View File

@@ -63,6 +63,9 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::Color:
setFontColor( attribute.asColor() );
break;
case PropertyId::BackgroundColor:
setFontBackgroundColor( attribute.asColor() );
break;
case PropertyId::TextShadowColor:
setFontShadowColor( attribute.asColor() );
break;
@@ -112,6 +115,8 @@ std::string UITextSpan::getPropertyString( const PropertyDefinition* propertyDef
return Graphics::Text::styleFlagToString( getFontStyle() );
case PropertyId::Color:
return getFontColor().toHexString();
case PropertyId::BackgroundColor:
return getFontBackgroundColor().toHexString();
case PropertyId::TextShadowColor:
return getFontShadowColor().toHexString();
case PropertyId::TextShadowOffset:
@@ -133,6 +138,7 @@ std::vector<PropertyId> UITextSpan::getPropertiesImplemented() const {
PropertyId::FontSize,
PropertyId::FontStyle,
PropertyId::Color,
PropertyId::BackgroundColor,
PropertyId::TextShadowColor,
PropertyId::TextShadowOffset,
PropertyId::TextStrokeWidth,
@@ -248,6 +254,19 @@ UITextSpan* UITextSpan::setFontColor( const Color& color ) {
return this;
}
const Color& UITextSpan::getFontBackgroundColor() const {
return mFontStyleConfig.getBackgroundColor();
}
UITextSpan* UITextSpan::setFontBackgroundColor( const Color& color ) {
if ( mFontStyleConfig.BackgroundColor != color ) {
mFontStyleConfig.BackgroundColor = color;
mStyleState |= StyleStateFontBackgroundColor;
onFontStyleChanged();
}
return this;
}
const Color& UITextSpan::getFontShadowColor() const {
return mFontStyleConfig.getFontShadowColor();
}
@@ -404,6 +423,12 @@ void UITextSpan::setInheritedStyle( const UIFontStyleConfig& fontStyleConfig ) {
fontStyleChanged = true;
}
if ( !hasFontBackgroundColor() &&
mFontStyleConfig.BackgroundColor != fontStyleConfig.BackgroundColor ) {
mFontStyleConfig.BackgroundColor = fontStyleConfig.BackgroundColor;
fontStyleChanged = true;
}
if ( fontChanged )
onFontChanged();
@@ -454,4 +479,8 @@ bool UITextSpan::hasFontShadowOffset() const {
return 0 != ( mStyleState & StyleStateFontShadowOffset );
}
bool UITextSpan::hasFontBackgroundColor() const {
return 0 != ( mStyleState & StyleStateFontBackgroundColor );
}
}} // namespace EE::UI

View File

@@ -1434,3 +1434,69 @@ UTEST( FontRendering, TextContiguousOffset ) {
runTest();
}
}
UTEST( FontRendering, TextBackgroundColor ) {
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
const auto runTest = [&]() {
auto win = Engine::instance()->createWindow(
WindowSettings( 512, 400, "eepp - Text Background Color", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ) );
ASSERT_TRUE_MSG( win->isOpen(), "Failed to create Window" );
win->setClearColor( RGB( 255, 255, 255 ) );
win->clear();
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
Vector2f pos{ 20, 20 };
Text text;
text.setFont( font );
text.setFontSize( 20 );
text.setFillColor( Color::Black );
text.setBackgroundColor( Color::Yellow );
text.setString( "Text with background color\nand multiple lines." );
text.draw( pos.x, pos.y );
pos.y += text.getTextHeight() + 20;
text.setAlign( TEXT_ALIGN_CENTER );
text.setString( "Centered text with\nbackground color." );
text.draw( pos.x, pos.y );
pos.y += text.getTextHeight() + 20;
text.setAlign( TEXT_ALIGN_LEFT );
text.setLineWrapMode( LineWrapMode::Word );
text.setMaxWrapWidth( 200 );
text.setString(
"Wrapped text with background color that should only cover the text area." );
text.draw( pos.x, pos.y );
pos.y += text.getTextHeight() + 20;
text.setLineWrapMode( LineWrapMode::NoWrap );
text.setBackgroundColor( Color::cyan );
text.setString( " " ); // Only spaces
text.draw( pos.x, pos.y );
compareImages( utest_state, utest_result, win, "eepp-text-background-color" );
Engine::destroySingleton();
};
UTEST_PRINT_STEP( "Text Shaper disabled" );
{
BoolScopedOp op( Text::TextShaperEnabled, false );
runTest();
}
UTEST_PRINT_STEP( "Text Shaper enabled" );
{
BoolScopedOp op( Text::TextShaperEnabled, true );
runTest();
UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" );
BoolScopedOp op2( Text::TextShaperOptimizations, false );
runTest();
}
}