diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp index 5cdf1db37..2413114d9 100644 Binary files a/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp and b/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp differ diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp index bbbb9fc64..e127945eb 100644 Binary files a/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp and b/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp differ diff --git a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp index cf3e3084d..41e5aa69c 100644 Binary files a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp and b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp differ diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp index 6e340e504..3185e52e1 100644 --- a/include/eepp/graphics/richtext.hpp +++ b/include/eepp/graphics/richtext.hpp @@ -35,7 +35,7 @@ class EE_API RichText : public Drawable { void addSpan( const String& text, const FontStyleConfig& style ); void addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding, Float lineHeight = 0, bool isAtomic = false ); + const Rectf& padding, Float lineHeight = 0 ); /** * @brief Adds a text span with individual style parameters. @@ -83,9 +83,9 @@ class EE_API RichText : public Drawable { struct CustomBlock { Sizef size; - bool isBlock{ false }; UI::CSSFloat floatType{ UI::CSSFloat::None }; UI::CSSClear clearType{ UI::CSSClear::None }; + bool isLineBreak{ false }; }; struct SpanBlock { @@ -93,7 +93,6 @@ class EE_API RichText : public Drawable { Rectf margin; Rectf padding; Float lineHeight{ 0 }; - bool isAtomic{ false }; }; using Block = std::variant, CustomBlock>; @@ -107,12 +106,13 @@ class EE_API RichText : public Drawable { /** * @brief Adds a custom size spacer into the text flow. * @param size The physical dimensions of the spacer. - * @param isBlock Whether this spacer acts as a block-level element. */ - void addCustomSize( const Sizef& size, bool isBlock = false, - UI::CSSFloat floatType = UI::CSSFloat::None, + void addCustomSize( const Sizef& size, UI::CSSFloat floatType = UI::CSSFloat::None, UI::CSSClear clearType = UI::CSSClear::None ); + /** @brief Adds a virtual line break that is not associated with a DOM text character. */ + void addLineBreak(); + /** @return The list of blocks. */ const std::vector& getBlocks() { return mBlocks; } diff --git a/include/eepp/ui/uihtmltextarea.hpp b/include/eepp/ui/uihtmltextarea.hpp index a14bfb2ff..b7487de19 100644 --- a/include/eepp/ui/uihtmltextarea.hpp +++ b/include/eepp/ui/uihtmltextarea.hpp @@ -15,6 +15,8 @@ class EE_API UIHTMLTextArea : public UITextEdit { virtual bool isType( const Uint32& type ) const; + virtual bool isInlineDisplay() const; + virtual bool applyProperty( const StyleSheetProperty& attribute ); virtual std::string getPropertyString( const PropertyDefinition* propertyDef, diff --git a/include/eepp/ui/uitextspan.hpp b/include/eepp/ui/uitextspan.hpp index 0bc02f180..1cb9a1e11 100644 --- a/include/eepp/ui/uitextspan.hpp +++ b/include/eepp/ui/uitextspan.hpp @@ -150,8 +150,6 @@ class EE_API UITextSpan : public UIRichText { explicit UITextSpan( const std::string& tag = "span" ); - virtual void drawBorder(); - virtual void onTextChanged(); virtual void onFontChanged(); diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 29870a845..b2ddb9e10 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -812,7 +812,7 @@ class EE_API UIWidget : public UINode { * * @return True if this widget is inline-level. */ - bool isInlineDisplay() const; + virtual bool isInlineDisplay() const; /** @return The index of this element among its sibling elements. */ Uint32 getElementIndex() const; diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index 85725ec64..24f238ebd 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -302,14 +302,14 @@ Sizef RichText::getPixelsSize() { } void RichText::addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding, Float lineHeight, bool isAtomic ) { + const Rectf& padding, Float lineHeight ) { if ( text.empty() && margin == Rectf::Zero && padding == Rectf::Zero && lineHeight == 0 ) return; auto span = std::make_shared(); span->setString( text ); span->setStyleConfig( style ); - mBlocks.push_back( SpanBlock{ span, margin, padding, lineHeight, isAtomic } ); + mBlocks.push_back( SpanBlock{ span, margin, padding, lineHeight } ); invalidateLayout(); } @@ -320,9 +320,13 @@ void RichText::addDrawable( std::shared_ptr drawable ) { invalidateLayout(); } -void RichText::addCustomSize( const Sizef& size, bool isBlock, UI::CSSFloat floatType, - UI::CSSClear clearType ) { - mBlocks.push_back( CustomBlock{ size, isBlock, floatType, clearType } ); +void RichText::addCustomSize( const Sizef& size, UI::CSSFloat floatType, UI::CSSClear clearType ) { + mBlocks.push_back( CustomBlock{ size, floatType, clearType, false } ); + invalidateLayout(); +} + +void RichText::addLineBreak() { + mBlocks.push_back( CustomBlock{ Sizef::Zero, UI::CSSFloat::None, UI::CSSClear::None, true } ); invalidateLayout(); } @@ -443,15 +447,8 @@ Float RichText::getMaxIntrinsicWidth() { } else if ( auto pDrawable = std::get_if>( &block ) ) { curX += ( *pDrawable )->getPixelsSize().getWidth(); } else if ( auto pSize = std::get_if( &block ) ) { - if ( pSize->isBlock ) { - if ( curX > 0 ) { - maxW = std::max( maxW, curX ); - curX = 0; - } - maxW = std::max( maxW, pSize->size.getWidth() ); - } else { + if ( !pSize->isLineBreak ) curX += pSize->size.getWidth(); - } } } maxW = std::max( maxW, curX ); @@ -512,18 +509,6 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; - if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { - Float extraRight = pText->margin.Right + pText->padding.Right; - Float fullTextWidth = span->getTextWidth(); - if ( curX + fullTextWidth + extraRight > mMaxWidth ) { - maxWidth = std::max( maxWidth, curX - extraLeft ); - mLines.push_back( RenderParagraph() ); - curX = extraLeft; - if ( !mLines.empty() ) - mLines.back().width += extraLeft; - } - } - Uint32 textHints = span->getTextHints(); // Compute where lines break within this text span. @@ -537,7 +522,6 @@ void RichText::updateLayout() { wrapInfo.wraps.push_back( span->getString().size() ); // Emit a RenderSpan for each segment, wrapping to new lines as needed. - Float atomicMaxX = 0; for ( size_t i = 0; i < wrapInfo.wraps.size() - 1; ++i ) { size_t startIdx = wrapInfo.wraps[i]; size_t endIdx = wrapInfo.wraps[i + 1]; @@ -559,7 +543,7 @@ void RichText::updateLayout() { RenderSpan renderSpan{ SpanBlock{ renderSpanText, pText->margin, pText->padding, - pText->lineHeight, pText->isAtomic }, + pText->lineHeight }, { curX, 0 }, Sizef( spanWidth, height ), curCharIdx, @@ -574,8 +558,6 @@ void RichText::updateLayout() { curX += spanWidth; currentLine.width += spanWidth; - if ( pText->isAtomic ) - atomicMaxX = std::max( atomicMaxX, curX ); } // After the last segment, add trailing margin and check if the @@ -584,8 +566,6 @@ void RichText::updateLayout() { Float extraRight = pText->margin.Right + pText->padding.Right; curX += extraRight; mLines.back().width += extraRight; - if ( pText->isAtomic ) - atomicMaxX = std::max( atomicMaxX, curX ); if ( !isNewline && mMaxWidth > 0 && curX > mMaxWidth ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); @@ -609,43 +589,28 @@ void RichText::updateLayout() { curX = 0; } } - - // Atomic (inline-block) spans reserve the width of their widest line - // so subsequent content does not flow beside a shorter last line. - if ( pText->isAtomic && atomicMaxX > curX ) { - curX = atomicMaxX; - if ( !mLines.empty() ) - mLines.back().width = std::max( mLines.back().width, curX ); - } - - // If the inline-block spanned multiple lines, force a new line - // so trailing content starts below the entire block. - if ( pText->isAtomic && wrapInfo.wraps.size() > 2 && curX > 0 ) { - maxWidth = std::max( maxWidth, curX ); - mLines.push_back( RenderParagraph() ); - curX = 0; - } } else { // Drawable or CustomBlock (non-float). Sizef blockSize; - bool isBlock = false; + bool isLineBreak = false; if ( auto pDrawable = std::get_if>( &block ) ) { auto& drawable = *pDrawable; blockSize = drawable ? drawable->getPixelsSize() : Sizef(); } else if ( auto pSize = std::get_if( &block ) ) { blockSize = pSize->size; - isBlock = pSize->isBlock; + isLineBreak = pSize->isLineBreak; } - // Block elements force a line break before themselves. - if ( isBlock && curX > 0 ) { + if ( isLineBreak ) { maxWidth = std::max( maxWidth, curX ); - mLines.push_back( RenderParagraph() ); + if ( !mLines.back().spans.empty() ) + mLines.push_back( RenderParagraph() ); curX = 0; + continue; } // Inline elements that don't fit wrap to the next line. - if ( mMaxWidth > 0 && !isBlock && + if ( mMaxWidth > 0 && ( curX + blockSize.getWidth() >= mMaxWidth || curX >= mMaxWidth ) && curX > 0 ) { maxWidth = std::max( maxWidth, curX ); @@ -665,8 +630,7 @@ void RichText::updateLayout() { curX += blockSize.getWidth(); currentLine.width += blockSize.getWidth(); - // Block elements also force a line break after themselves. - if ( ( mMaxWidth > 0 && curX >= mMaxWidth ) || isBlock ) { + if ( mMaxWidth > 0 && curX >= mMaxWidth ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); curX = 0; @@ -768,6 +732,19 @@ void RichText::updateLayout() { return floatRightEdge( y ) - floatLeftEdge( y ); }; + auto activeFloatBottom = [&]( Float y ) -> Float { + Float bottom = y; + for ( const auto& f : leftFloats ) { + if ( y >= f.Top && y < f.Bottom ) + bottom = std::max( bottom, f.Bottom ); + } + for ( const auto& f : rightFloats ) { + if ( y >= f.Top && y < f.Bottom ) + bottom = std::max( bottom, f.Bottom ); + } + return bottom; + }; + // Advances curY past the bottom of active floats specified by clearType. // Returns true if curY was moved. auto clearFloats = [&]( UI::CSSClear clearType ) -> bool { @@ -819,18 +796,6 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; - if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { - Float extraRight = pText->margin.Right + pText->padding.Right; - Float fullTextWidth = span->getTextWidth(); - if ( curX + fullTextWidth + extraRight > mMaxWidth ) { - maxWidth = std::max( maxWidth, curX - extraLeft ); - mLines.push_back( RenderParagraph() ); - curX = extraLeft; - if ( !mLines.empty() ) - mLines.back().width += extraLeft; - } - } - // Shift curX inside to the left edge — text starts // to the right of any left floats. Float le = floatLeftEdge( curY ); @@ -852,7 +817,6 @@ void RichText::updateLayout() { wrapInfo.wraps.back() != (Float)span->getString().size() ) wrapInfo.wraps.push_back( span->getString().size() ); - Float atomicMaxX = 0; for ( size_t i = 0; i < wrapInfo.wraps.size() - 1; ++i ) { size_t startIdx = wrapInfo.wraps[i]; size_t endIdx = wrapInfo.wraps[i + 1]; @@ -871,8 +835,8 @@ void RichText::updateLayout() { Float spanWidth = renderSpanText->getTextWidth(); RenderSpan renderSpan{ - SpanBlock{ renderSpanText, pText->margin, pText->padding, pText->lineHeight, - pText->isAtomic }, + SpanBlock{ renderSpanText, pText->margin, pText->padding, + pText->lineHeight }, { curX, 0 }, Sizef( spanWidth, height ), curCharIdx, @@ -888,8 +852,6 @@ void RichText::updateLayout() { curX += spanWidth; currentLine.width += spanWidth; - if ( pText->isAtomic ) - atomicMaxX = std::max( atomicMaxX, curX ); } // After the last segment, add trailing margin and check if the @@ -898,8 +860,6 @@ void RichText::updateLayout() { Float extraRight = pText->margin.Right + pText->padding.Right; curX += extraRight; mLines.back().width += extraRight; - if ( pText->isAtomic ) - atomicMaxX = std::max( atomicMaxX, curX ); if ( effW > 0 && effW < 1e9f && curX > effW ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); @@ -923,25 +883,10 @@ void RichText::updateLayout() { curX = 0; } } - - // Atomic (inline-block) spans reserve the width of their widest line. - if ( pText->isAtomic && atomicMaxX > curX ) { - curX = atomicMaxX; - if ( !mLines.empty() ) - mLines.back().width = std::max( mLines.back().width, curX ); - } - - // If the inline-block spanned multiple lines, force a new line - // so trailing content starts below the entire block. - if ( pText->isAtomic && wrapInfo.wraps.size() > 2 && curX > 0 ) { - maxWidth = std::max( maxWidth, curX ); - mLines.push_back( RenderParagraph() ); - curX = 0; - } } else { // ── Drawable or CustomBlock ──────────────────────────── Sizef blockSize; - bool isBlock = false; + bool isLineBreak = false; UI::CSSFloat floatType = UI::CSSFloat::None; UI::CSSClear clearType = UI::CSSClear::None; if ( auto pDrawable = std::get_if>( &block ) ) { @@ -949,9 +894,20 @@ void RichText::updateLayout() { blockSize = drawable ? drawable->getPixelsSize() : Sizef(); } else if ( auto pSize = std::get_if( &block ) ) { blockSize = pSize->size; - isBlock = pSize->isBlock; floatType = pSize->floatType; clearType = pSize->clearType; + isLineBreak = pSize->isLineBreak; + } + + if ( isLineBreak ) { + maxWidth = std::max( maxWidth, curX ); + if ( !mLines.back().spans.empty() ) { + curY += mLines.back().height; + mLines.push_back( RenderParagraph() ); + mLines.back().y = curY; + } + curX = 0; + continue; } // ── Clear: advance curY past active floats ───────────── @@ -959,6 +915,7 @@ void RichText::updateLayout() { if ( clearFloats( clearType ) ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); + mLines.back().y = curY; curX = 0; } } @@ -988,6 +945,7 @@ void RichText::updateLayout() { mLines.push_back( RenderParagraph() ); curX = 0; curY = maxBottom; + mLines.back().y = curY; posX = floatLeftEdge( curY ); } } @@ -1005,6 +963,7 @@ void RichText::updateLayout() { mLines.push_back( RenderParagraph() ); curX = 0; curY = maxBottom; + mLines.back().y = curY; re = floatRightEdge( curY ); le = floatLeftEdge( curY ); posX = re - blockSize.getWidth(); @@ -1028,45 +987,28 @@ void RichText::updateLayout() { rightFloats.push_back( fr ); } else { // ── Normal (non-float) block ──────────────────── - Float flowX = curX; if ( curX < le ) curX = le; - // Block elements force a line break before - // only when there is inline-flow content on the line. - if ( isBlock && flowX > 0 ) { - maxWidth = std::max( maxWidth, flowX ); - mLines.push_back( RenderParagraph() ); - curX = 0; - if ( curX < le ) - curX = le; - } - Float effW = effectiveMaxWidthAt( curY ); - // When a block does not fit beside active floats, - // advance curY below them. - if ( isBlock && effW > 0 && effW < 1e9f && - curX + blockSize.getWidth() > effW + 0.01f && curX > 0 ) { - Float maxBottom = curY; - for ( auto& f : leftFloats ) - maxBottom = std::max( maxBottom, f.Bottom ); - for ( auto& f : rightFloats ) - maxBottom = std::max( maxBottom, f.Bottom ); + if ( effW > 0 && effW < 1e9f && blockSize.getWidth() > effW + 0.01f ) { + Float maxBottom = activeFloatBottom( curY ); if ( maxBottom > curY ) { maxWidth = std::max( maxWidth, curX ); - mLines.push_back( RenderParagraph() ); + if ( !mLines.back().spans.empty() ) + mLines.push_back( RenderParagraph() ); curX = 0; curY = maxBottom; + mLines.back().y = curY; le = floatLeftEdge( curY ); - if ( curX < le ) - curX = le; + effW = effectiveMaxWidthAt( curY ); } } // Wrap if the block doesn't fit in the available width // (narrowed by active floats). - if ( effW > 0 && effW < 1e9f && !isBlock && + if ( effW > 0 && effW < 1e9f && ( curX + blockSize.getWidth() >= effW || curX >= effW ) && curX > 0 ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); @@ -1085,8 +1027,7 @@ void RichText::updateLayout() { curX += blockSize.getWidth(); currentLine.width += blockSize.getWidth(); - // Block elements or overflow force a line break after. - if ( ( effW > 0 && effW < 1e9f && curX >= effW ) || isBlock ) { + if ( effW > 0 && effW < 1e9f && curX >= effW ) { maxWidth = std::max( maxWidth, curX ); mLines.push_back( RenderParagraph() ); curX = 0; @@ -1106,6 +1047,8 @@ void RichText::updateLayout() { // text-align only affects inline-flow content, not floated elements. Float accumY = 0; for ( auto& line : mLines ) { + if ( line.y > accumY ) + accumY = line.y; line.y = accumY; Float xOffset = 0; @@ -1136,8 +1079,12 @@ void RichText::updateLayout() { if ( offsetY < 0 ) offsetY = 0; // Float spans keep their edge-aligned x; only inline-flow spans shift. - if ( !isFloat ) + if ( isFloat ) { + span.position.y = 0; + continue; + } else { span.position.x += xOffset; + } span.position.y = offsetY; maxLineHeight = std::max( maxLineHeight, offsetY + span.size.getHeight() ); } @@ -1149,7 +1096,13 @@ void RichText::updateLayout() { accumY += line.height; } - mSize = Sizef( maxWidth, accumY ); + Float floatBoundsBottom = 0; + for ( const auto& f : leftFloats ) + floatBoundsBottom = std::max( floatBoundsBottom, f.Bottom ); + for ( const auto& f : rightFloats ) + floatBoundsBottom = std::max( floatBoundsBottom, f.Bottom ); + + mSize = Sizef( maxWidth, std::max( accumY, floatBoundsBottom ) ); mTotalCharacterCount = curCharIdx; mNeedsLayoutUpdate = false; } diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 60c33f5dc..f3c128c7b 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -159,8 +159,10 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { while ( currentSpan < line.spans.size() ) { const auto& span = line.spans[currentSpan]; currentSpan++; - if ( std::holds_alternative( span.block ) ) - return &span; + if ( auto custom = std::get_if( &span.block ) ) { + if ( !custom->isLineBreak ) + return &span; + } } currentSpan = 0; currentLine++; @@ -351,21 +353,6 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { widget->setPixelsPosition( targetPos - offset ); bounds = Rectf( targetPos, span->size ); - - if ( widget->isType( UI_TYPE_TEXTSPAN ) && - widget->asType()->isInlineBlock() ) { - Rectf pad = widget->getPixelsPadding(); - bounds.Left -= pad.Left; - bounds.Top -= pad.Top; - bounds.Right += pad.Right; - bounds.Bottom += pad.Bottom; - Vector2f boundsPos = bounds.getPosition(); - widget->setPixelsPosition( boundsPos - offset ); - if ( bounds.getSize() != widget->getPixelsSize() ) { - widget->setPixelsSize( bounds.getSize() ); - mResizedCount++; - } - } } } } diff --git a/src/eepp/ui/uihtmlinput.cpp b/src/eepp/ui/uihtmlinput.cpp index 8b3cd021a..8796b1cf0 100644 --- a/src/eepp/ui/uihtmlinput.cpp +++ b/src/eepp/ui/uihtmlinput.cpp @@ -17,6 +17,7 @@ UIHTMLInput* UIHTMLInput::New() { UIHTMLInput::UIHTMLInput() : UIHTMLWidget( "input" ) { mFlags |= UI_HTML_ELEMENT; + mDisplay = CSSDisplay::InlineBlock; mWidthPolicy = SizePolicy::WrapContent; mHeightPolicy = SizePolicy::WrapContent; createChildWidget(); diff --git a/src/eepp/ui/uihtmltextarea.cpp b/src/eepp/ui/uihtmltextarea.cpp index 43a311015..1dcff9f9f 100644 --- a/src/eepp/ui/uihtmltextarea.cpp +++ b/src/eepp/ui/uihtmltextarea.cpp @@ -25,6 +25,10 @@ bool UIHTMLTextArea::isType( const Uint32& type ) const { return UIHTMLTextArea::getType() == type || UITextEdit::isType( type ); } +bool UIHTMLTextArea::isInlineDisplay() const { + return true; +} + bool UIHTMLTextArea::applyProperty( const StyleSheetProperty& attribute ) { if ( !attribute.getPropertyDefinition() ) return false; diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 8f773c61b..0b051542b 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -291,6 +292,13 @@ void UIRichText::draw() { mSize.getHeight() - contentOffset.Top - contentOffset.Bottom ); } + if ( isType( UI_TYPE_TEXTSPAN ) && !asType()->isMergeable() && + asType()->getFontBackgroundColor() != Color::Transparent ) { + Primitives p; + p.setColor( asType()->getFontBackgroundColor() ); + p.drawRectangle( Rectf( mScreenPos.trunc(), mSize.floor() ), 0.f, Vector2f::One ); + } + mRichText.draw( std::trunc( mScreenPos.x ) + (int)contentOffset.Left, std::trunc( mScreenPos.y ) + (int)contentOffset.Top, Vector2f::One, 0.f, getBlendMode() ); @@ -824,7 +832,14 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri : true; bool lastSpanEndsWithSpace = false; Float maxWidth = 0; - if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { + bool isInlineBlockTextSpan = + container->isType( UI_TYPE_TEXTSPAN ) && container->asType()->isInlineBlock(); + if ( isInlineBlockTextSpan && mode == IntrinsicMode::None && + container->getPixelsSize().getWidth() > 0 ) { + maxWidth = container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right; + } else if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { maxWidth = container->getMatchParentWidth() - container->getPixelsContentOffset().Left - container->getPixelsContentOffset().Right; } else { @@ -859,7 +874,9 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri if ( !selfSpan->getText().empty() && !selfSpan->isMergeable() && NULL != selfSpan->getFontStyleConfig().Font ) { String::View selfText = selfSpan->getText().view(); - richText.addSpan( selfText, selfSpan->getFontStyleConfig() ); + FontStyleConfig style = selfSpan->getFontStyleConfig(); + style.BackgroundColor = Color::Transparent; + richText.addSpan( selfText, style ); if ( shouldCollapse ) lastSpanEndsWithSpace = !selfText.empty() && selfText.back() == ' '; } @@ -992,7 +1009,7 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri if ( !spanText.empty() ) { richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding, - spanLineHeight, span->isInlineBlock() ); + spanLineHeight ); span->setLayoutCharCount( spanText.length() ); if ( shouldCollapse ) lastSpanEndsWithSpace = spanText.back() == ' '; @@ -1065,8 +1082,7 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri } if ( widget->isType( UI_TYPE_TEXTSPAN ) && - widget->asType()->isInlineBlock() && - widget->getPixelsSize().getWidth() == 0 ) + widget->asType()->isInlineBlock() ) widget->asType()->updateLayout(); } @@ -1094,10 +1110,28 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri floatType = widget->asType()->getCSSFloat(); clearType = widget->asType()->getCSSClear(); } + bool isNormalFlowBlock = isBlock && floatType == CSSFloat::None; + + if ( isNormalFlowBlock ) + richText.addLineBreak(); richText.addCustomSize( Sizef( w + margin.Left + margin.Right, size.getHeight() + margin.Top + margin.Bottom ), - isBlock, floatType, clearType ); + floatType, clearType ); + + if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() && + widget->asType()->getRichTextPtr() ) + widget->asType()->getRichTextPtr()->updateLayout(); + + if ( isNormalFlowBlock ) + richText.addLineBreak(); + else if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() && + widget->asType()->getRichTextPtr() && + widget->asType()->getRichTextPtr()->getLines().size() > 1 ) + richText.addLineBreak(); + lastSpanEndsWithSpace = false; } } diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index 6162b78b7..c25f3d611 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -62,38 +62,13 @@ bool UITextSpan::isType( const Uint32& type ) const { } bool UITextSpan::isMergeable() const { - if ( mDisplay == CSSDisplay::Inline ) - return true; - if ( mDisplay == CSSDisplay::InlineBlock ) { - if ( getText().empty() || NULL == getFontStyleConfig().Font ) - return false; - if ( getLayoutWidthPolicy() == SizePolicy::Fixed || - getLayoutHeightPolicy() == SizePolicy::Fixed ) - return false; - return true; - } - return false; + return mDisplay == CSSDisplay::Inline; } bool UITextSpan::isInlineBlock() const { return mDisplay == CSSDisplay::InlineBlock; } -void UITextSpan::drawBorder() { - if ( ( mFlags & UI_BORDER ) && NULL != mBorder ) { - mBorder->setAlpha( mAlpha ); - if ( isInlineBlock() ) { - mBorder->draw( Vector2f( std::trunc( mScreenPos.x ), std::trunc( mScreenPos.y ) ), - Sizef( std::floor( mSize.x ), std::floor( mSize.y ) ) ); - } else { - mBorder->draw( { std::trunc( mScreenPos.x - mPaddingPx.Left ), - std::trunc( mScreenPos.y - mPaddingPx.Top ) }, - { std::floor( mSize.x + mPaddingPx.Left + mPaddingPx.Right ), - std::floor( mSize.y + mPaddingPx.Top + mPaddingPx.Bottom ) } ); - } - } -} - void UITextSpan::draw() { if ( !isMergeable() ) UIRichText::draw(); diff --git a/src/tests/unit_tests/richtext_tests.cpp b/src/tests/unit_tests/richtext_tests.cpp index 7ab26093f..96cebf282 100644 --- a/src/tests/unit_tests/richtext_tests.cpp +++ b/src/tests/unit_tests/richtext_tests.cpp @@ -396,6 +396,23 @@ UTEST( UIRichText, IntegrationAndLayoutVerification ) { destroyRichTextScene( sceneNode ); } +UTEST( RichText, VirtualLineBreakSeparatesCustomBlocks ) { + RichText rt; + rt.addCustomSize( { 10, 5 } ); + rt.addLineBreak(); + rt.addCustomSize( { 20, 7 } ); + rt.updateLayout(); + + const auto& lines = rt.getLines(); + ASSERT_EQ( lines.size(), (size_t)2 ); + ASSERT_EQ( lines[0].spans.size(), (size_t)1 ); + ASSERT_EQ( lines[1].spans.size(), (size_t)1 ); + EXPECT_EQ( lines[0].spans[0].position.x, 0 ); + EXPECT_EQ( lines[1].spans[0].position.x, 0 ); + EXPECT_EQ( lines[1].y, lines[0].height ); + EXPECT_EQ( rt.getSize().getHeight(), lines[0].height + lines[1].height ); +} + UTEST( UIRichText, selection ) { auto sceneNode = createRichTextScene(); ASSERT_TRUE( sceneNode != nullptr ); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index a61bb608e..d70a29d62 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -541,6 +541,40 @@ UTEST( UIHTMLTextArea, rowsColsAttribute ) { Engine::destroySingleton(); } +UTEST( UIHTML, FormControlsDefaultInlineBlock ) { + init_ui_test(); + auto* sceneNode = SceneManager::instance()->getUISceneNode(); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( +
+ + + + +
+ )html" ) ); + sceneNode->updateDirtyLayouts(); + + auto* i1 = sceneNode->getRoot()->find( "i1" )->asType(); + auto* i2 = sceneNode->getRoot()->find( "i2" )->asType(); + auto* t1 = sceneNode->getRoot()->find( "t1" )->asType(); + auto* t2 = sceneNode->getRoot()->find( "t2" )->asType(); + + ASSERT_TRUE( i1 != nullptr ); + ASSERT_TRUE( i2 != nullptr ); + ASSERT_TRUE( t1 != nullptr ); + ASSERT_TRUE( t2 != nullptr ); + + EXPECT_EQ( i1->getDisplay(), CSSDisplay::InlineBlock ); + EXPECT_EQ( i2->getDisplay(), CSSDisplay::InlineBlock ); + + EXPECT_EQ( i1->getPixelsPosition().y, i2->getPixelsPosition().y ); + EXPECT_LT( i1->getPixelsPosition().x, i2->getPixelsPosition().x ); + EXPECT_EQ( t1->getPixelsPosition().y, t2->getPixelsPosition().y ); + EXPECT_LT( t1->getPixelsPosition().x, t2->getPixelsPosition().x ); + + Engine::destroySingleton(); +} + UTEST( UIHTMLTable, tableLayoutFixed ) { Engine::instance()->createWindow( WindowSettings( 1024, 650, "HTML Tables Test", WindowStyle::Default, WindowBackend::Default,