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,