Add text-transform support for UIRichText and UITextSpan.

This commit is contained in:
Martín Lucas Golini
2026-05-22 00:41:30 -03:00
parent 0c742d6431
commit ba3576aa8c
5 changed files with 89 additions and 3 deletions

View File

@@ -2,6 +2,7 @@
#define EE_UI_UIRICHTEXT_HPP #define EE_UI_UIRICHTEXT_HPP
#include <eepp/graphics/richtext.hpp> #include <eepp/graphics/richtext.hpp>
#include <eepp/graphics/texttransform.hpp>
#include <eepp/ui/uihtmlwidget.hpp> #include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uilayout.hpp> #include <eepp/ui/uilayout.hpp>
@@ -141,6 +142,10 @@ class EE_API UIRichText : public UIHTMLWidget {
void setTextSelectionEnabled( bool active ); void setTextSelectionEnabled( bool active );
const TextTransform::Value& getTextTransform() const;
void setTextTransform( const TextTransform::Value& textTransform );
const Color& getSelectionBackColor() const; const Color& getSelectionBackColor() const;
void setSelectionBackColor( const Color& color ); void setSelectionBackColor( const Color& color );
@@ -169,6 +174,7 @@ class EE_API UIRichText : public UIHTMLWidget {
mutable Float mTextIndentPxCache{ 0 }; mutable Float mTextIndentPxCache{ 0 };
mutable bool mTextIndentPxDirty{ true }; mutable bool mTextIndentPxDirty{ true };
WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse }; WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse };
TextTransform::Value mTextTransform{ TextTransform::None };
explicit UIRichText( const std::string& tag = "richtext" ); explicit UIRichText( const std::string& tag = "richtext" );

View File

@@ -117,6 +117,7 @@ class EE_API UITextSpan : public UIRichText {
StyleStateFontShadowColor = 1 << 6, StyleStateFontShadowColor = 1 << 6,
StyleStateFontShadowOffset = 1 << 7, StyleStateFontShadowOffset = 1 << 7,
StyleStateFontBackgroundColor = 1 << 8, StyleStateFontBackgroundColor = 1 << 8,
StyleStateTextTransform = 1 << 9,
StyleStateAll = 0xFFFFFFFF StyleStateAll = 0xFFFFFFFF
}; };
@@ -130,6 +131,8 @@ class EE_API UITextSpan : public UIRichText {
bool hasFontShadowOffset() const; bool hasFontShadowOffset() const;
bool hasFontBackgroundColor() const; bool hasFontBackgroundColor() const;
bool hasTextTransform() const;
SpanHitBoxes& getHitBoxes(); SpanHitBoxes& getHitBoxes();
const SpanHitBoxes& getHitBoxes() const; const SpanHitBoxes& getHitBoxes() const;

View File

@@ -1761,7 +1761,7 @@ workspace "eepp"
files { "src/benchmarks/*.cpp" } files { "src/benchmarks/*.cpp" }
incdirs { "src/thirdparty" } incdirs { "src/thirdparty" }
build_link_configuration( "eepp-benchmarks", true ) build_link_configuration( "eepp-benchmarks", true )
project "eepp-unit_tests" project "eepp-unit_tests"
kind "ConsoleApp" kind "ConsoleApp"
targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests") targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests")

View File

@@ -410,6 +410,9 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::WhiteSpaceCollapse: case PropertyId::WhiteSpaceCollapse:
setWhiteSpaceCollapse( toWhiteSpaceCollapse( attribute.value() ) ); setWhiteSpaceCollapse( toWhiteSpaceCollapse( attribute.value() ) );
break; break;
case PropertyId::TextTransform:
setTextTransform( TextTransform::fromString( attribute.asString() ) );
break;
default: default:
return UIHTMLWidget::applyProperty( attribute ); return UIHTMLWidget::applyProperty( attribute );
} }
@@ -461,6 +464,8 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
return mTextIndentEq.empty() ? "0" : mTextIndentEq; return mTextIndentEq.empty() ? "0" : mTextIndentEq;
case PropertyId::WhiteSpaceCollapse: case PropertyId::WhiteSpaceCollapse:
return fromWhiteSpaceCollapse( mWhiteSpaceCollapse ); return fromWhiteSpaceCollapse( mWhiteSpaceCollapse );
case PropertyId::TextTransform:
return TextTransform::toString( getTextTransform() );
default: default:
return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex ); return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex );
} }
@@ -475,7 +480,8 @@ std::vector<PropertyId> UIRichText::getPropertiesImplemented() const {
PropertyId::TextAlign, PropertyId::SelectionColor, PropertyId::TextAlign, PropertyId::SelectionColor,
PropertyId::SelectionBackColor, PropertyId::TextSelection, PropertyId::SelectionBackColor, PropertyId::TextSelection,
PropertyId::TextDecoration, PropertyId::LineHeight, PropertyId::TextDecoration, PropertyId::LineHeight,
PropertyId::TextIndent, PropertyId::WhiteSpaceCollapse }; PropertyId::TextIndent, PropertyId::WhiteSpaceCollapse,
PropertyId::TextTransform };
props.insert( props.end(), local.begin(), local.end() ); props.insert( props.end(), local.begin(), local.end() );
return props; return props;
} }
@@ -733,6 +739,18 @@ Float UIRichText::getTextIndentPx() const {
return mTextIndentPxCache; return mTextIndentPxCache;
} }
const TextTransform::Value& UIRichText::getTextTransform() const {
return mTextTransform;
}
void UIRichText::setTextTransform( const TextTransform::Value& textTransform ) {
if ( textTransform != mTextTransform ) {
mTextTransform = textTransform;
notifyLayoutAttrChange();
notifyLayoutAttrChangeParent();
}
}
void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) { void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
beginAttributesTransaction(); beginAttributesTransaction();
@@ -1060,11 +1078,46 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
richText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later richText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later
} }
auto getEffectiveTextTransform = []( Node* node ) -> TextTransform::Value {
while ( node ) {
if ( node->isType( UI_TYPE_RICHTEXT ) || node->isType( UI_TYPE_TEXTSPAN ) ) {
auto tt = node->asType<UIRichText>()->getTextTransform();
if ( tt != TextTransform::None )
return tt;
}
node = node->getParent();
}
return TextTransform::None;
};
auto applyTextTransform = []( String& text, TextTransform::Value tt ) {
switch ( tt ) {
case TextTransform::LowerCase:
text = text.toLower();
break;
case TextTransform::UpperCase:
text = text.toUpper();
break;
case TextTransform::Capitalize:
text = text.capitalize();
break;
default:
break;
}
};
if ( container->isType( UI_TYPE_TEXTSPAN ) ) { if ( container->isType( UI_TYPE_TEXTSPAN ) ) {
UITextSpan* selfSpan = container->asType<UITextSpan>(); UITextSpan* selfSpan = container->asType<UITextSpan>();
if ( !selfSpan->getText().empty() && !selfSpan->isInline() && if ( !selfSpan->getText().empty() && !selfSpan->isInline() &&
NULL != selfSpan->getFontStyleConfig().Font ) { NULL != selfSpan->getFontStyleConfig().Font ) {
String::View selfText = selfSpan->getText().view(); String::View selfText = selfSpan->getText().view();
String transformed;
auto tt = getEffectiveTextTransform( selfSpan );
if ( tt != TextTransform::None ) {
transformed = selfText;
applyTextTransform( transformed, tt );
selfText = transformed.view();
}
FontStyleConfig style = selfSpan->getFontStyleConfig(); FontStyleConfig style = selfSpan->getFontStyleConfig();
style.BackgroundColor = Color::Transparent; style.BackgroundColor = Color::Transparent;
richText.addSpan( selfText, style, Rectf::Zero, Rectf::Zero, 0, richText.addSpan( selfText, style, Rectf::Zero, Rectf::Zero, 0,
@@ -1135,6 +1188,12 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
if ( shouldCollapse && lastSpanEndsWithSpace && !text.empty() && text[0] == ' ' ) if ( shouldCollapse && lastSpanEndsWithSpace && !text.empty() && text[0] == ' ' )
text = text.substr( 1 ); text = text.substr( 1 );
{
auto tt = getEffectiveTextTransform( textNode );
if ( tt != TextTransform::None )
applyTextTransform( text, tt );
}
if ( text.empty() ) { if ( text.empty() ) {
textNode->setLayoutCharCount( 0 ); textNode->setLayoutCharCount( 0 );
return; return;
@@ -1229,6 +1288,14 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
spanText[0] == ' ' ) spanText[0] == ' ' )
spanText = spanText.substr( 1 ); spanText = spanText.substr( 1 );
String transformed;
auto tt = getEffectiveTextTransform( span );
if ( tt != TextTransform::None ) {
transformed = spanText;
applyTextTransform( transformed, tt );
spanText = transformed.view();
}
if ( !spanText.empty() ) { if ( !spanText.empty() ) {
richText.addInlineText( spanText, span->getFontStyleConfig(), Rectf::Zero, richText.addInlineText( spanText, span->getFontStyleConfig(), Rectf::Zero,
Rectf::Zero, 0, {} ); Rectf::Zero, 0, {} );

View File

@@ -126,6 +126,9 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::TextDecoration: case PropertyId::TextDecoration:
setTextDecoration( attribute.asTextDecoration() ); setTextDecoration( attribute.asTextDecoration() );
break; break;
case PropertyId::TextTransform:
setTextTransform( TextTransform::fromString( attribute.asString() ) );
break;
default: default:
return UIRichText::applyProperty( attribute ); return UIRichText::applyProperty( attribute );
} }
@@ -163,6 +166,8 @@ std::string UITextSpan::getPropertyString( const PropertyDefinition* propertyDef
return getOutlineColor().toHexString(); return getOutlineColor().toHexString();
case PropertyId::TextDecoration: case PropertyId::TextDecoration:
return Text::styleFlagToString( getTextDecoration() ); return Text::styleFlagToString( getTextDecoration() );
case PropertyId::TextTransform:
return TextTransform::toString( getTextTransform() );
default: default:
return UIRichText::getPropertyString( propertyDef, propertyIndex ); return UIRichText::getPropertyString( propertyDef, propertyIndex );
} }
@@ -180,7 +185,8 @@ std::vector<PropertyId> UITextSpan::getPropertiesImplemented() const {
PropertyId::TextShadowOffset, PropertyId::TextShadowOffset,
PropertyId::TextStrokeWidth, PropertyId::TextStrokeWidth,
PropertyId::TextStrokeColor, PropertyId::TextStrokeColor,
PropertyId::TextDecoration }; PropertyId::TextDecoration,
PropertyId::TextTransform };
props.insert( props.end(), local.begin(), local.end() ); props.insert( props.end(), local.begin(), local.end() );
return props; return props;
} }
@@ -566,6 +572,10 @@ bool UITextSpan::hasFontBackgroundColor() const {
return 0 != ( mStyleState & StyleStateFontBackgroundColor ); return 0 != ( mStyleState & StyleStateFontBackgroundColor );
} }
bool UITextSpan::hasTextTransform() const {
return 0 != ( mStyleState & StyleStateTextTransform );
}
SpanHitBoxes& UITextSpan::getHitBoxes() { SpanHitBoxes& UITextSpan::getHitBoxes() {
return mHitBoxes; return mHitBoxes;
} }