From ba3576aa8c3bead34b50ee9c74592a06d7a3ab2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 22 May 2026 00:41:30 -0300 Subject: [PATCH] Add text-transform support for UIRichText and UITextSpan. --- include/eepp/ui/uirichtext.hpp | 6 +++ include/eepp/ui/uitextspan.hpp | 3 ++ premake5.lua | 2 +- src/eepp/ui/uirichtext.cpp | 69 +++++++++++++++++++++++++++++++++- src/eepp/ui/uitextspan.cpp | 12 +++++- 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp index 37a5ab31f..aabc3be68 100644 --- a/include/eepp/ui/uirichtext.hpp +++ b/include/eepp/ui/uirichtext.hpp @@ -2,6 +2,7 @@ #define EE_UI_UIRICHTEXT_HPP #include +#include #include #include @@ -141,6 +142,10 @@ class EE_API UIRichText : public UIHTMLWidget { void setTextSelectionEnabled( bool active ); + const TextTransform::Value& getTextTransform() const; + + void setTextTransform( const TextTransform::Value& textTransform ); + const Color& getSelectionBackColor() const; void setSelectionBackColor( const Color& color ); @@ -169,6 +174,7 @@ class EE_API UIRichText : public UIHTMLWidget { mutable Float mTextIndentPxCache{ 0 }; mutable bool mTextIndentPxDirty{ true }; WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse }; + TextTransform::Value mTextTransform{ TextTransform::None }; explicit UIRichText( const std::string& tag = "richtext" ); diff --git a/include/eepp/ui/uitextspan.hpp b/include/eepp/ui/uitextspan.hpp index debc9af3d..6169af213 100644 --- a/include/eepp/ui/uitextspan.hpp +++ b/include/eepp/ui/uitextspan.hpp @@ -117,6 +117,7 @@ class EE_API UITextSpan : public UIRichText { StyleStateFontShadowColor = 1 << 6, StyleStateFontShadowOffset = 1 << 7, StyleStateFontBackgroundColor = 1 << 8, + StyleStateTextTransform = 1 << 9, StyleStateAll = 0xFFFFFFFF }; @@ -130,6 +131,8 @@ class EE_API UITextSpan : public UIRichText { bool hasFontShadowOffset() const; bool hasFontBackgroundColor() const; + bool hasTextTransform() const; + SpanHitBoxes& getHitBoxes(); const SpanHitBoxes& getHitBoxes() const; diff --git a/premake5.lua b/premake5.lua index 25e8891fb..03c788d6f 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1761,7 +1761,7 @@ workspace "eepp" files { "src/benchmarks/*.cpp" } incdirs { "src/thirdparty" } build_link_configuration( "eepp-benchmarks", true ) - + project "eepp-unit_tests" kind "ConsoleApp" targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests") diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 3d89cbaa5..676569d5d 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -410,6 +410,9 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::WhiteSpaceCollapse: setWhiteSpaceCollapse( toWhiteSpaceCollapse( attribute.value() ) ); break; + case PropertyId::TextTransform: + setTextTransform( TextTransform::fromString( attribute.asString() ) ); + break; default: return UIHTMLWidget::applyProperty( attribute ); } @@ -461,6 +464,8 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef return mTextIndentEq.empty() ? "0" : mTextIndentEq; case PropertyId::WhiteSpaceCollapse: return fromWhiteSpaceCollapse( mWhiteSpaceCollapse ); + case PropertyId::TextTransform: + return TextTransform::toString( getTextTransform() ); default: return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex ); } @@ -475,7 +480,8 @@ std::vector UIRichText::getPropertiesImplemented() const { PropertyId::TextAlign, PropertyId::SelectionColor, PropertyId::SelectionBackColor, PropertyId::TextSelection, PropertyId::TextDecoration, PropertyId::LineHeight, - PropertyId::TextIndent, PropertyId::WhiteSpaceCollapse }; + PropertyId::TextIndent, PropertyId::WhiteSpaceCollapse, + PropertyId::TextTransform }; props.insert( props.end(), local.begin(), local.end() ); return props; } @@ -733,6 +739,18 @@ Float UIRichText::getTextIndentPx() const { 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 ) { 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 } + auto getEffectiveTextTransform = []( Node* node ) -> TextTransform::Value { + while ( node ) { + if ( node->isType( UI_TYPE_RICHTEXT ) || node->isType( UI_TYPE_TEXTSPAN ) ) { + auto tt = node->asType()->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 ) ) { UITextSpan* selfSpan = container->asType(); if ( !selfSpan->getText().empty() && !selfSpan->isInline() && NULL != selfSpan->getFontStyleConfig().Font ) { 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(); style.BackgroundColor = Color::Transparent; 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] == ' ' ) text = text.substr( 1 ); + { + auto tt = getEffectiveTextTransform( textNode ); + if ( tt != TextTransform::None ) + applyTextTransform( text, tt ); + } + if ( text.empty() ) { textNode->setLayoutCharCount( 0 ); return; @@ -1229,6 +1288,14 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri spanText[0] == ' ' ) 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() ) { richText.addInlineText( spanText, span->getFontStyleConfig(), Rectf::Zero, Rectf::Zero, 0, {} ); diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index 41532a831..a5147ad9f 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -126,6 +126,9 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::TextDecoration: setTextDecoration( attribute.asTextDecoration() ); break; + case PropertyId::TextTransform: + setTextTransform( TextTransform::fromString( attribute.asString() ) ); + break; default: return UIRichText::applyProperty( attribute ); } @@ -163,6 +166,8 @@ std::string UITextSpan::getPropertyString( const PropertyDefinition* propertyDef return getOutlineColor().toHexString(); case PropertyId::TextDecoration: return Text::styleFlagToString( getTextDecoration() ); + case PropertyId::TextTransform: + return TextTransform::toString( getTextTransform() ); default: return UIRichText::getPropertyString( propertyDef, propertyIndex ); } @@ -180,7 +185,8 @@ std::vector UITextSpan::getPropertiesImplemented() const { PropertyId::TextShadowOffset, PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, - PropertyId::TextDecoration }; + PropertyId::TextDecoration, + PropertyId::TextTransform }; props.insert( props.end(), local.begin(), local.end() ); return props; } @@ -566,6 +572,10 @@ bool UITextSpan::hasFontBackgroundColor() const { return 0 != ( mStyleState & StyleStateFontBackgroundColor ); } +bool UITextSpan::hasTextTransform() const { + return 0 != ( mStyleState & StyleStateTextTransform ); +} + SpanHitBoxes& UITextSpan::getHitBoxes() { return mHitBoxes; }