diff --git a/bin/unit_tests/assets/html/float_li.html b/bin/unit_tests/assets/html/float_li.html
new file mode 100644
index 000000000..51c6a6bc1
--- /dev/null
+++ b/bin/unit_tests/assets/html/float_li.html
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp
index 98b2f12ed..6f03f2c3b 100644
--- a/include/eepp/graphics/richtext.hpp
+++ b/include/eepp/graphics/richtext.hpp
@@ -142,7 +142,8 @@ class EE_API RichText : public Drawable {
*/
void addCustomSize( const Sizef& size, InlineFloat floatType = InlineFloat::None,
InlineClear clearType = InlineClear::None, Float baseline = -1.f,
- const BaselineAlignValue& baselineAlign = {}, InlineSource source = {} );
+ const BaselineAlignValue& baselineAlign = {}, InlineSource source = {},
+ bool isBlock = false );
/** @brief Adds a virtual line break that is not associated with a DOM text character. */
void addLineBreak();
@@ -185,6 +186,7 @@ class EE_API RichText : public Drawable {
InlineFloat floatType{ InlineFloat::None };
InlineClear clearType{ InlineClear::None };
bool isLineBreak{ false };
+ bool isBlock{ false };
InlinePath inlinePath;
Vector2f position; // Local position relative to RichText origin
Sizef size;
@@ -298,6 +300,7 @@ class EE_API RichText : public Drawable {
InlineFloat floatType{ InlineFloat::None };
InlineClear clearType{ InlineClear::None };
bool isLineBreak{ false };
+ bool isBlock{ false };
BaselineAlignValue baselineAlign;
};
diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp
index 0eedd5e67..abad0681d 100644
--- a/src/eepp/graphics/richtext.cpp
+++ b/src/eepp/graphics/richtext.cpp
@@ -1197,7 +1197,7 @@ class RichTextInlineLayouter {
else
rightFloats.push_back( fr );
} else {
- if ( curX < le )
+ if ( !metrics.isBlock && curX < le )
curX = le;
Float startSpacing = 0.f;
@@ -1209,9 +1209,10 @@ class RichTextInlineLayouter {
bool hadLineContentBeforeSpacing = !result.lines.back().spans.empty();
addInlineSpacingToCurrentLine( result, curX, startSpacing );
- Float effW = effectiveMaxWidthAt( curY );
+ Float effW = metrics.isBlock ? maxLayoutWidth : effectiveMaxWidthAt( curY );
- if ( effW > 0 && effW < 1e9f && metrics.size.getWidth() > effW + 0.01f ) {
+ if ( !metrics.isBlock && effW > 0 && effW < 1e9f &&
+ metrics.size.getWidth() > effW + 0.01f ) {
Float maxBottom = activeFloatBottom( curY );
if ( maxBottom > curY ) {
maxWidth = std::max( maxWidth, curX );
@@ -1225,7 +1226,7 @@ class RichTextInlineLayouter {
}
}
- if ( effW > 0 && effW < 1e9f &&
+ if ( !metrics.isBlock && effW > 0 && effW < 1e9f &&
( curX + metrics.size.getWidth() >= effW || curX >= effW ) && curX > 0 &&
hadLineContentBeforeSpacing ) {
maxWidth = std::max( maxWidth, curX );
@@ -1446,6 +1447,7 @@ class RichTextInlineLayouter {
Sizef size;
Float baseline{ 0.f };
bool isLineBreak{ false };
+ bool isBlock{ false };
RichText::InlineFloat floatType{ RichText::InlineFloat::None };
RichText::InlineClear clearType{ RichText::InlineClear::None };
};
@@ -1518,6 +1520,7 @@ class RichTextInlineLayouter {
run.payload.floatType = box.floatType;
run.payload.clearType = box.clearType;
run.payload.isLineBreak = box.isLineBreak;
+ run.payload.isBlock = box.isBlock;
run.payload.baselineAlign = box.baselineAlign;
run.payload.inlinePath = path;
run.payload._leafIndex = nextLeafIndex++;
@@ -1647,6 +1650,7 @@ class RichTextInlineLayouter {
metrics.size = payload.size;
metrics.baseline = payload.baseline;
metrics.isLineBreak = payload.isLineBreak;
+ metrics.isBlock = payload.isBlock;
metrics.floatType = payload.floatType;
metrics.clearType = payload.clearType;
}
@@ -1856,7 +1860,7 @@ void RichText::addDrawable( std::shared_ptr drawable ) {
void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineClear clearType,
Float baseline, const BaselineAlignValue& baselineAlign,
- InlineSource source ) {
+ InlineSource source, bool isBlock ) {
Float usedBaseline = baseline >= 0.f ? baseline : size.getHeight();
InlineItem item;
@@ -1866,6 +1870,7 @@ void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineCl
box.baseline = usedBaseline;
box.floatType = floatType;
box.clearType = clearType;
+ box.isBlock = isBlock;
box.baselineAlign = baselineAlign;
item.data = std::move( box );
resolveInlinePath( mInlineItems, mInlinePath )->push_back( std::move( item ) );
diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp
index 064ec85f1..a658c635c 100644
--- a/src/eepp/ui/blocklayouter.cpp
+++ b/src/eepp/ui/blocklayouter.cpp
@@ -216,6 +216,11 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
return bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom;
};
+ auto isFloatingWidget = []( UIWidget* widget ) {
+ return widget->isType( UI_TYPE_HTML_WIDGET ) &&
+ widget->asType()->getCSSFloat() != CSSFloat::None;
+ };
+
auto expandBounds = [&]( Rectf& bounds, bool& valid, const Rectf& rect ) {
if ( !valid ) {
bounds = rect;
@@ -443,7 +448,8 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
atomicBounds.Top + margin.Top );
widget->setPixelsPosition( targetPos - offset );
- if ( widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
+ if ( !isFloatingWidget( widget ) &&
+ widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
Float contentWidth = eemax(
0.f, mContainer->getPixelsSize().getWidth() - contentOffset.Left -
@@ -471,7 +477,8 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
contentOffset.Top + lineY + span->position.y + margin.Top );
widget->setPixelsPosition( targetPos - offset );
- if ( widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
+ if ( !isFloatingWidget( widget ) &&
+ widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
// Stretch match-parent children only after the wrap-content parent has its
// final used width. During RichText measurement this width may still be a
diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp
index 79a32372b..b1c2e2fd9 100644
--- a/src/eepp/ui/uirichtext.cpp
+++ b/src/eepp/ui/uirichtext.cpp
@@ -1396,18 +1396,40 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
floatType = widget->asType()->getCSSFloat();
clearType = widget->asType()->getCSSClear();
}
- bool isNormalFlowBlock = isBlock && floatType == CSSFloat::None;
+ bool isFloating = floatType != CSSFloat::None;
+ bool isNormalFlowBlock = isBlock && !isFloating;
+ bool shrinkToFitFloat =
+ isFloating && widget->getLayoutWidthPolicy() == SizePolicy::MatchParent;
if ( isNormalFlowBlock )
richText.addLineBreak();
+ if ( shrinkToFitFloat && mode == IntrinsicMode::None ) {
+ container->onAutoSizeChild( widget );
+ Float availableWidth = 0.f;
+ if ( container->getPixelsSize().getWidth() > 0 ) {
+ availableWidth = eemax( 0.f, container->getPixelsSize().getWidth() -
+ container->getPixelsContentOffset().Left -
+ container->getPixelsContentOffset().Right -
+ margin.Left - margin.Right );
+ }
+ Float preferredMin = widget->getMinIntrinsicWidth();
+ Float preferred = widget->getMaxIntrinsicWidth();
+ Float shrinkWidth = preferred;
+ if ( availableWidth > 0 )
+ shrinkWidth = eemin( eemax( preferredMin, availableWidth ), preferred );
+ widget->setPixelsSize( shrinkWidth, widget->getPixelsSize().getHeight() );
+ size = widget->getPixelsSize();
+ w = shrinkWidth;
+ }
+
Sizef customSize( w + margin.Left + margin.Right,
size.getHeight() + margin.Top + margin.Bottom );
richText.addCustomSize( customSize, toRichTextFloat( floatType ),
toRichTextClear( clearType ),
getAtomicInlineBoxBaseline( widget, size, margin ),
toRichTextBaselineAlign( getWidgetBaselineAlign( widget ) ),
- toRichTextWidgetSource( widget ) );
+ toRichTextWidgetSource( widget ), isNormalFlowBlock );
if ( widget->isType( UI_TYPE_TEXTSPAN ) &&
widget->asType()->isInlineBlock() &&
diff --git a/src/tests/unit_tests/uihtml_float_tests.cpp b/src/tests/unit_tests/uihtml_float_tests.cpp
index e95eb2c07..8bbb35ed7 100644
--- a/src/tests/unit_tests/uihtml_float_tests.cpp
+++ b/src/tests/unit_tests/uihtml_float_tests.cpp
@@ -156,6 +156,7 @@ UTEST( UIHTMLFloat, floatLeft_TextWrapsRight ) {
inlineChild->setParent( container );
inlineChild->setPixelsSize( 80, 30 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ inlineChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts();
@@ -188,6 +189,7 @@ UTEST( UIHTMLFloat, floatRight_TextFlowsLeft ) {
inlineChild->setParent( container );
inlineChild->setPixelsSize( 80, 30 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ inlineChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts();
@@ -201,6 +203,40 @@ UTEST( UIHTMLFloat, floatRight_TextFlowsLeft ) {
Engine::destroySingleton();
}
+UTEST( UIHTMLFloat, rightFloatDoesNotDisplaceFollowingNormalBlock ) {
+ init_float_test();
+ UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
+
+ UIRichText* container = UIRichText::New();
+ container->setParent( sceneNode->getRoot() );
+ container->setPixelsSize( 600, 400 );
+ container->setPixelsPosition( 10, 10 );
+ container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
+
+ UIHTMLWidget* side = UIHTMLWidget::New();
+ side->setParent( container );
+ side->setPixelsSize( 100, 100 );
+ side->setCSSFloat( CSSFloat::Right );
+ side->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+
+ UIHTMLWidget* content = UIHTMLWidget::New();
+ content->setParent( container );
+ content->setPixelsSize( 0, 30 );
+ content->setLayoutPixelsMarginRight( 120 );
+ content->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed );
+
+ sceneNode->updateDirtyLayouts();
+
+ Vector2f sidePos = side->convertToWorldSpace( { 0, 0 } );
+ Vector2f contentPos = content->convertToWorldSpace( { 0, 0 } );
+
+ EXPECT_NEAR( sidePos.y, contentPos.y, 1.f );
+ EXPECT_NEAR( contentPos.x, container->convertToWorldSpace( { 0, 0 } ).x, 1.f );
+ EXPECT_NEAR( content->getPixelsSize().getWidth(), 480.f, 1.f );
+
+ Engine::destroySingleton();
+}
+
UTEST( UIHTMLFloat, twoFloatsLeft_StackHorizontally ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
@@ -327,6 +363,7 @@ UTEST( UIHTMLFloat, clearLeft_OnlyJumpsPastLeftFloats ) {
inlineChild->setParent( container );
inlineChild->setPixelsSize( 50, 20 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ inlineChild->setDisplay( CSSDisplay::InlineBlock );
UIHTMLWidget* clearLeftChild = UIHTMLWidget::New();
clearLeftChild->setParent( container );
@@ -402,6 +439,7 @@ UTEST( UIHTMLFloat, mixedLeftRight_ContentBetween ) {
middleChild->setParent( container );
middleChild->setPixelsSize( 150, 30 );
middleChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ middleChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts();
@@ -438,6 +476,7 @@ UTEST( UIHTMLFloat, floatWrapsContentBelowWhenTooWide ) {
wideChild->setParent( container );
wideChild->setPixelsSize( 400, 25 );
wideChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ wideChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts();
@@ -469,6 +508,7 @@ UTEST( UIHTMLFloat, floatLeft_InlineBlockBeside ) {
inlineBlock->setParent( container );
inlineBlock->setPixelsSize( 80, 30 );
inlineBlock->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ inlineBlock->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts();
diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp
index 5169688b3..c63f62ea4 100644
--- a/src/tests/unit_tests/uihtml_tests.cpp
+++ b/src/tests/unit_tests/uihtml_tests.cpp
@@ -2439,3 +2439,38 @@ UTEST( FontTrueType, glyphScaleZeroDimensionsNoCrash ) {
Engine::destroySingleton();
}
+
+UTEST( UIHTML, LiFloatLeft ) {
+ auto win = Engine::instance()->createWindow(
+ WindowSettings( 1024, 653, "li float left", WindowStyle::Default, WindowBackend::Default,
+ 32, {}, 1, false, true ),
+ ContextSettings( false, 0, 0, GLv_default, true, false ) );
+ FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
+
+ UI::UISceneNode* sceneNode = init_test_inline_block();
+
+ sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
+
+ std::string html;
+ FileSystem::fileGet( "assets/html/float_li.html", html );
+ sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) );
+ win->setClearColor( Color::White );
+
+ win->getInput()->update();
+ SceneManager::instance()->update();
+
+ win->clear();
+ SceneManager::instance()->draw();
+ win->display();
+
+ auto livec = sceneNode->getRoot()->findAllByTag( "li" );
+
+ ASSERT_GT( livec.size(), (size_t)0 );
+
+ auto refY = livec[0]->getPixelsPosition().y;
+
+ for ( size_t i = 1; i < livec.size(); i++ )
+ EXPECT_NEAR( refY, livec[i]->getPixelsPosition().y, 1.f );
+
+ Engine::destroySingleton();
+}