From 3f304e9c8034dfe52663d6c7161aee6fa8ca374e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 6 Jul 2026 11:48:35 -0300 Subject: [PATCH] Float minor fix. --- src/eepp/graphics/richtext.cpp | 12 ++- src/eepp/ui/blocklayouter.cpp | 33 +++++++- src/tests/unit_tests/uihtml_float_tests.cpp | 26 ++++++ src/tests/unit_tests/uihtml_tests.cpp | 89 +++++++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index c64675134..0c43a8c9e 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -1365,12 +1365,18 @@ class RichTextInlineLayouter { } Float floatBoundsBottom = 0; - for ( size_t i = externalLeftFloatCount; i < leftFloats.size(); ++i ) + Float floatBoundsRight = 0; + for ( size_t i = externalLeftFloatCount; i < leftFloats.size(); ++i ) { floatBoundsBottom = std::max( floatBoundsBottom, leftFloats[i].Bottom ); - for ( size_t i = externalRightFloatCount; i < rightFloats.size(); ++i ) + floatBoundsRight = std::max( floatBoundsRight, leftFloats[i].Right ); + } + for ( size_t i = externalRightFloatCount; i < rightFloats.size(); ++i ) { floatBoundsBottom = std::max( floatBoundsBottom, rightFloats[i].Bottom ); + floatBoundsRight = std::max( floatBoundsRight, rightFloats[i].Right ); + } - result.size = Sizef( maxWidth, std::max( accumY, floatBoundsBottom ) ); + result.size = + Sizef( std::max( maxWidth, floatBoundsRight ), std::max( accumY, floatBoundsBottom ) ); result.totalCharacterCount = curCharIdx; return result; } diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 228c08a3c..5d4e10270 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -128,9 +128,38 @@ void BlockLayouter::updateLayout() { positionRichTextChildren( rt ); + Sizef contentSize = rt->getSize(); + if ( mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent || + mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { + // RichText computes float placement from custom-block sizes captured before the + // corresponding widgets are positioned and may be resized by nested layout. For the + // intentional HTML-layer hit-test compatibility behavior, wrap-content parents must grow to + // the final floated child bounds, including the parent's padding/content offset. + const Rectf contentOffset = mContainer->getPixelsContentOffset(); + Node* child = mContainer->getFirstChild(); + while ( child != nullptr ) { + if ( child->isWidget() && child->isType( UI_TYPE_HTML_WIDGET ) ) { + auto* childWidget = child->asType(); + if ( childWidget->isVisible() && !childWidget->isOutOfFlow() && + childWidget->getCSSFloat() != CSSFloat::None ) { + const Rectf margin = childWidget->getNormalFlowLayoutPixelsMargin(); + const Vector2f pos = childWidget->getPixelsPosition(); + const Sizef size = childWidget->getPixelsSize(); + contentSize.setWidth( + std::max( contentSize.getWidth(), pos.x - margin.Left + size.getWidth() + + margin.Right - contentOffset.Left ) ); + contentSize.setHeight( std::max( contentSize.getHeight(), + pos.y - margin.Top + size.getHeight() + + margin.Bottom - contentOffset.Top ) ); + } + } + child = child->getNextNode(); + } + } + Float totW = mContainer->getPixelsSize().getWidth(); if ( mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { - totW = rt->getSize().getWidth() + mContainer->getPixelsContentOffset().Left + + totW = contentSize.getWidth() + mContainer->getPixelsContentOffset().Left + mContainer->getPixelsContentOffset().Right; if ( !mContainer->getMaxWidthEq().empty() && totW > mContainer->getMaxSizePx().getWidth() ) mContainer->setClipType( ClipType::ContentBox ); @@ -157,7 +186,7 @@ void BlockLayouter::updateLayout() { Float totH = mContainer->getPixelsSize().getHeight(); if ( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { - totH = rt->getSize().getHeight() + mContainer->getPixelsContentOffset().Top + + totH = contentSize.getHeight() + mContainer->getPixelsContentOffset().Top + mContainer->getPixelsContentOffset().Bottom; if ( !mContainer->getMaxHeightEq().empty() && totH > mContainer->getMaxSizePx().getHeight() ) diff --git a/src/tests/unit_tests/uihtml_float_tests.cpp b/src/tests/unit_tests/uihtml_float_tests.cpp index 4a860ecaa..d5a06eea2 100644 --- a/src/tests/unit_tests/uihtml_float_tests.cpp +++ b/src/tests/unit_tests/uihtml_float_tests.cpp @@ -1128,6 +1128,32 @@ UTEST( UIHTMLFloat, floatLeftNonHTMLwidget_NoCrash ) { Engine::destroySingleton(); } +UTEST( UIHTMLFloat, floatOnlyWrapContentParentIncludesPadding ) { + init_float_test(); + UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); + + UIRichText* container = UIRichText::New(); + container->setParent( sceneNode->getRoot() ); + container->setPixelsPosition( 10, 10 ); + container->setPaddingPixels( { 10, 10, 10, 10 } ); + container->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ); + + UIHTMLWidget* floatChild = UIHTMLWidget::New(); + floatChild->setParent( container ); + floatChild->setPixelsSize( 10, 10 ); + floatChild->setCSSFloat( CSSFloat::Left ); + floatChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + + sceneNode->updateDirtyLayouts(); + + EXPECT_NEAR( container->getPixelsSize().getWidth(), 30.f, 1.f ); + EXPECT_NEAR( container->getPixelsSize().getHeight(), 30.f, 1.f ); + EXPECT_NEAR( floatChild->getPixelsPosition().x, 10.f, 1.f ); + EXPECT_NEAR( floatChild->getPixelsPosition().y, 10.f, 1.f ); + + Engine::destroySingleton(); +} + UTEST( UIHTMLFloat, floatNotAffectedByTextAlignCenter ) { Engine::instance()->createWindow( WindowSettings( 800, 600, "Float + TextAlign Test", WindowStyle::Default, WindowBackend::Default, diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index cc51a36cf..215149ea9 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -3656,6 +3656,95 @@ UTEST( UIHTML, RedditHeaderPagenameBottomAlign ) { Engine::destroySingleton(); } +UTEST( UIHTML, PaddedHeaderWithNestedFloatsDoesNotOverlapClearedMain ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 1024, 653, "padded header nested floats", 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->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( + + + + + + +
+ + + )html" ) ); + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + + auto* header = sceneNode->getRoot()->find( "header" )->asType(); + auto* main = sceneNode->getRoot()->find( "main" )->asType(); + auto* branding = sceneNode->getRoot()->find( "branding" )->asType(); + auto* access = sceneNode->getRoot()->find( "access" )->asType(); + ASSERT_TRUE( header != nullptr ); + ASSERT_TRUE( main != nullptr ); + ASSERT_TRUE( branding != nullptr ); + ASSERT_TRUE( access != nullptr ); + + const Float headerBottom = + header->convertToWorldSpace( { 0, 0 } ).y + header->getPixelsSize().getHeight(); + const Float mainTop = main->convertToWorldSpace( { 0, 0 } ).y; + + EXPECT_GE( header->getPixelsSize().getHeight(), header->getPixelsContentOffset().Top + + branding->getPixelsSize().getHeight() + + access->getPixelsSize().getHeight() ); + EXPECT_GE( mainTop + 0.5f, headerBottom ); + + Engine::destroySingleton(); +} + UTEST( UIHTML, AnchorsSizing ) { auto win = Engine::instance()->createWindow( WindowSettings( 1024, 653, "anchors sizing", WindowStyle::Default, WindowBackend::Default,