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(); +}