float fixes.

This commit is contained in:
Martín Lucas Golini
2026-05-23 19:24:14 -03:00
parent d7807a1aa9
commit 49971701ed
7 changed files with 204 additions and 10 deletions
+82
View File
@@ -0,0 +1,82 @@
<!doctype html>
<html lang="en-us">
<head>
<style>
body {
max-width: 800px;
margin: auto;
padding: 0.2em;
line-height: 1.5em;
}
.pull-left {
float: left;
}
.pull-right {
float: right;
}
a {
color: #613dc1;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header {
position: relative;
width: 100%;
height: 1.75rem;
font-size: 0.875rem;
background: #613dc1;
border-bottom: 1px solid #000;
}
header ul,
header ol {
margin: 0;
padding: 0;
list-style: none;
}
header nav {
padding: 0 0.5rem;
}
header a {
color: #fff;
line-height: 1.75rem;
padding: 0 0.5rem;
}
header a:hover,
header .current a {
color: #fff;
}
</style>
</head>
<body>
<header>
<nav>
<ul>
<li class="pull-left">
<a href="https://spader.zone/" data-prefetched="true">home</a>
</li>
<li class="pull-left">
<a
href="https://buttondown.com/spader"
target="_blank"
rel="noopener"
>subscribe</a
>
</li>
<li class="pull-left">
<a href="https://spader.zone/index.xml" data-prefetched="true"
>rss</a
>
</li>
</ul>
</nav>
</header>
</body>
</html>
+4 -1
View File
@@ -142,7 +142,8 @@ class EE_API RichText : public Drawable {
*/ */
void addCustomSize( const Sizef& size, InlineFloat floatType = InlineFloat::None, void addCustomSize( const Sizef& size, InlineFloat floatType = InlineFloat::None,
InlineClear clearType = InlineClear::None, Float baseline = -1.f, 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. */ /** @brief Adds a virtual line break that is not associated with a DOM text character. */
void addLineBreak(); void addLineBreak();
@@ -185,6 +186,7 @@ class EE_API RichText : public Drawable {
InlineFloat floatType{ InlineFloat::None }; InlineFloat floatType{ InlineFloat::None };
InlineClear clearType{ InlineClear::None }; InlineClear clearType{ InlineClear::None };
bool isLineBreak{ false }; bool isLineBreak{ false };
bool isBlock{ false };
InlinePath inlinePath; InlinePath inlinePath;
Vector2f position; // Local position relative to RichText origin Vector2f position; // Local position relative to RichText origin
Sizef size; Sizef size;
@@ -298,6 +300,7 @@ class EE_API RichText : public Drawable {
InlineFloat floatType{ InlineFloat::None }; InlineFloat floatType{ InlineFloat::None };
InlineClear clearType{ InlineClear::None }; InlineClear clearType{ InlineClear::None };
bool isLineBreak{ false }; bool isLineBreak{ false };
bool isBlock{ false };
BaselineAlignValue baselineAlign; BaselineAlignValue baselineAlign;
}; };
+10 -5
View File
@@ -1197,7 +1197,7 @@ class RichTextInlineLayouter {
else else
rightFloats.push_back( fr ); rightFloats.push_back( fr );
} else { } else {
if ( curX < le ) if ( !metrics.isBlock && curX < le )
curX = le; curX = le;
Float startSpacing = 0.f; Float startSpacing = 0.f;
@@ -1209,9 +1209,10 @@ class RichTextInlineLayouter {
bool hadLineContentBeforeSpacing = !result.lines.back().spans.empty(); bool hadLineContentBeforeSpacing = !result.lines.back().spans.empty();
addInlineSpacingToCurrentLine( result, curX, startSpacing ); 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 ); Float maxBottom = activeFloatBottom( curY );
if ( maxBottom > curY ) { if ( maxBottom > curY ) {
maxWidth = std::max( maxWidth, curX ); 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 && ( curX + metrics.size.getWidth() >= effW || curX >= effW ) && curX > 0 &&
hadLineContentBeforeSpacing ) { hadLineContentBeforeSpacing ) {
maxWidth = std::max( maxWidth, curX ); maxWidth = std::max( maxWidth, curX );
@@ -1446,6 +1447,7 @@ class RichTextInlineLayouter {
Sizef size; Sizef size;
Float baseline{ 0.f }; Float baseline{ 0.f };
bool isLineBreak{ false }; bool isLineBreak{ false };
bool isBlock{ false };
RichText::InlineFloat floatType{ RichText::InlineFloat::None }; RichText::InlineFloat floatType{ RichText::InlineFloat::None };
RichText::InlineClear clearType{ RichText::InlineClear::None }; RichText::InlineClear clearType{ RichText::InlineClear::None };
}; };
@@ -1518,6 +1520,7 @@ class RichTextInlineLayouter {
run.payload.floatType = box.floatType; run.payload.floatType = box.floatType;
run.payload.clearType = box.clearType; run.payload.clearType = box.clearType;
run.payload.isLineBreak = box.isLineBreak; run.payload.isLineBreak = box.isLineBreak;
run.payload.isBlock = box.isBlock;
run.payload.baselineAlign = box.baselineAlign; run.payload.baselineAlign = box.baselineAlign;
run.payload.inlinePath = path; run.payload.inlinePath = path;
run.payload._leafIndex = nextLeafIndex++; run.payload._leafIndex = nextLeafIndex++;
@@ -1647,6 +1650,7 @@ class RichTextInlineLayouter {
metrics.size = payload.size; metrics.size = payload.size;
metrics.baseline = payload.baseline; metrics.baseline = payload.baseline;
metrics.isLineBreak = payload.isLineBreak; metrics.isLineBreak = payload.isLineBreak;
metrics.isBlock = payload.isBlock;
metrics.floatType = payload.floatType; metrics.floatType = payload.floatType;
metrics.clearType = payload.clearType; metrics.clearType = payload.clearType;
} }
@@ -1856,7 +1860,7 @@ void RichText::addDrawable( std::shared_ptr<Drawable> drawable ) {
void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineClear clearType, void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineClear clearType,
Float baseline, const BaselineAlignValue& baselineAlign, Float baseline, const BaselineAlignValue& baselineAlign,
InlineSource source ) { InlineSource source, bool isBlock ) {
Float usedBaseline = baseline >= 0.f ? baseline : size.getHeight(); Float usedBaseline = baseline >= 0.f ? baseline : size.getHeight();
InlineItem item; InlineItem item;
@@ -1866,6 +1870,7 @@ void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineCl
box.baseline = usedBaseline; box.baseline = usedBaseline;
box.floatType = floatType; box.floatType = floatType;
box.clearType = clearType; box.clearType = clearType;
box.isBlock = isBlock;
box.baselineAlign = baselineAlign; box.baselineAlign = baselineAlign;
item.data = std::move( box ); item.data = std::move( box );
resolveInlinePath( mInlineItems, mInlinePath )->push_back( std::move( item ) ); resolveInlinePath( mInlineItems, mInlinePath )->push_back( std::move( item ) );
+9 -2
View File
@@ -216,6 +216,11 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
return bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom; return bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom;
}; };
auto isFloatingWidget = []( UIWidget* widget ) {
return widget->isType( UI_TYPE_HTML_WIDGET ) &&
widget->asType<UIHTMLWidget>()->getCSSFloat() != CSSFloat::None;
};
auto expandBounds = [&]( Rectf& bounds, bool& valid, const Rectf& rect ) { auto expandBounds = [&]( Rectf& bounds, bool& valid, const Rectf& rect ) {
if ( !valid ) { if ( !valid ) {
bounds = rect; bounds = rect;
@@ -443,7 +448,8 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
atomicBounds.Top + margin.Top ); atomicBounds.Top + margin.Top );
widget->setPixelsPosition( targetPos - offset ); widget->setPixelsPosition( targetPos - offset );
if ( widget->getLayoutWidthPolicy() == SizePolicy::MatchParent && if ( !isFloatingWidget( widget ) &&
widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
Float contentWidth = eemax( Float contentWidth = eemax(
0.f, mContainer->getPixelsSize().getWidth() - contentOffset.Left - 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 ); contentOffset.Top + lineY + span->position.y + margin.Top );
widget->setPixelsPosition( targetPos - offset ); widget->setPixelsPosition( targetPos - offset );
if ( widget->getLayoutWidthPolicy() == SizePolicy::MatchParent && if ( !isFloatingWidget( widget ) &&
widget->getLayoutWidthPolicy() == SizePolicy::MatchParent &&
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
// Stretch match-parent children only after the wrap-content parent has its // Stretch match-parent children only after the wrap-content parent has its
// final used width. During RichText measurement this width may still be a // final used width. During RichText measurement this width may still be a
+24 -2
View File
@@ -1396,18 +1396,40 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
floatType = widget->asType<UIHTMLWidget>()->getCSSFloat(); floatType = widget->asType<UIHTMLWidget>()->getCSSFloat();
clearType = widget->asType<UIHTMLWidget>()->getCSSClear(); clearType = widget->asType<UIHTMLWidget>()->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 ) if ( isNormalFlowBlock )
richText.addLineBreak(); 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, Sizef customSize( w + margin.Left + margin.Right,
size.getHeight() + margin.Top + margin.Bottom ); size.getHeight() + margin.Top + margin.Bottom );
richText.addCustomSize( customSize, toRichTextFloat( floatType ), richText.addCustomSize( customSize, toRichTextFloat( floatType ),
toRichTextClear( clearType ), toRichTextClear( clearType ),
getAtomicInlineBoxBaseline( widget, size, margin ), getAtomicInlineBoxBaseline( widget, size, margin ),
toRichTextBaselineAlign( getWidgetBaselineAlign( widget ) ), toRichTextBaselineAlign( getWidgetBaselineAlign( widget ) ),
toRichTextWidgetSource( widget ) ); toRichTextWidgetSource( widget ), isNormalFlowBlock );
if ( widget->isType( UI_TYPE_TEXTSPAN ) && if ( widget->isType( UI_TYPE_TEXTSPAN ) &&
widget->asType<UITextSpan>()->isInlineBlock() && widget->asType<UITextSpan>()->isInlineBlock() &&
@@ -156,6 +156,7 @@ UTEST( UIHTMLFloat, floatLeft_TextWrapsRight ) {
inlineChild->setParent( container ); inlineChild->setParent( container );
inlineChild->setPixelsSize( 80, 30 ); inlineChild->setPixelsSize( 80, 30 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
inlineChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts(); sceneNode->updateDirtyLayouts();
@@ -188,6 +189,7 @@ UTEST( UIHTMLFloat, floatRight_TextFlowsLeft ) {
inlineChild->setParent( container ); inlineChild->setParent( container );
inlineChild->setPixelsSize( 80, 30 ); inlineChild->setPixelsSize( 80, 30 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
inlineChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts(); sceneNode->updateDirtyLayouts();
@@ -201,6 +203,40 @@ UTEST( UIHTMLFloat, floatRight_TextFlowsLeft ) {
Engine::destroySingleton(); 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 ) { UTEST( UIHTMLFloat, twoFloatsLeft_StackHorizontally ) {
init_float_test(); init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
@@ -327,6 +363,7 @@ UTEST( UIHTMLFloat, clearLeft_OnlyJumpsPastLeftFloats ) {
inlineChild->setParent( container ); inlineChild->setParent( container );
inlineChild->setPixelsSize( 50, 20 ); inlineChild->setPixelsSize( 50, 20 );
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
inlineChild->setDisplay( CSSDisplay::InlineBlock );
UIHTMLWidget* clearLeftChild = UIHTMLWidget::New(); UIHTMLWidget* clearLeftChild = UIHTMLWidget::New();
clearLeftChild->setParent( container ); clearLeftChild->setParent( container );
@@ -402,6 +439,7 @@ UTEST( UIHTMLFloat, mixedLeftRight_ContentBetween ) {
middleChild->setParent( container ); middleChild->setParent( container );
middleChild->setPixelsSize( 150, 30 ); middleChild->setPixelsSize( 150, 30 );
middleChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); middleChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
middleChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts(); sceneNode->updateDirtyLayouts();
@@ -438,6 +476,7 @@ UTEST( UIHTMLFloat, floatWrapsContentBelowWhenTooWide ) {
wideChild->setParent( container ); wideChild->setParent( container );
wideChild->setPixelsSize( 400, 25 ); wideChild->setPixelsSize( 400, 25 );
wideChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); wideChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
wideChild->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts(); sceneNode->updateDirtyLayouts();
@@ -469,6 +508,7 @@ UTEST( UIHTMLFloat, floatLeft_InlineBlockBeside ) {
inlineBlock->setParent( container ); inlineBlock->setParent( container );
inlineBlock->setPixelsSize( 80, 30 ); inlineBlock->setPixelsSize( 80, 30 );
inlineBlock->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); inlineBlock->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
inlineBlock->setDisplay( CSSDisplay::InlineBlock );
sceneNode->updateDirtyLayouts(); sceneNode->updateDirtyLayouts();
+35
View File
@@ -2439,3 +2439,38 @@ UTEST( FontTrueType, glyphScaleZeroDimensionsNoCrash ) {
Engine::destroySingleton(); 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();
}