Float minor fix.

This commit is contained in:
Martín Lucas Golini
2026-07-06 11:48:35 -03:00
parent c9add7047b
commit 3f304e9c80
4 changed files with 155 additions and 5 deletions

View File

@@ -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;
}

View File

@@ -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<UIHTMLWidget>();
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() )

View File

@@ -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,

View File

@@ -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>
<head>
<style>
#header {
padding: 30px 0 0 0;
width: 940px;
}
#masthead {
width: 940px;
}
#branding {
float: left;
width: 940px;
height: 267px;
}
#access {
display: block;
float: left;
width: 940px;
}
#access ul {
list-style: none;
margin: 0;
padding: 0;
}
#access li {
float: left;
}
#access a {
display: block;
line-height: 38px;
padding: 0 10px;
}
#main {
clear: both;
padding: 40px 0 0 0;
width: 940px;
}
</style>
</head>
<body>
<div id="header">
<div id="masthead">
<div id="branding"></div>
<div id="access">
<ul>
<li><a>Home</a></li>
</ul>
</div>
</div>
</div>
<div id="main"></div>
</body>
</html>
)html" ) );
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
auto* header = sceneNode->getRoot()->find( "header" )->asType<UIHTMLWidget>();
auto* main = sceneNode->getRoot()->find( "main" )->asType<UIHTMLWidget>();
auto* branding = sceneNode->getRoot()->find( "branding" )->asType<UIHTMLWidget>();
auto* access = sceneNode->getRoot()->find( "access" )->asType<UIHTMLWidget>();
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,