Fix flex items losing their stretched cross size

Preserve the final cross-axis size assigned to auto-sized stretched flex
items when their block layout runs. Keep intrinsic flex measurement
separate so content changes can still update item sizing correctly.

Add an HTML regression test covering a short sidebar stretched alongside
taller content.
This commit is contained in:
Martín Lucas Golini
2026-07-30 11:46:54 -03:00
parent 9f65377c50
commit d61c8a7582
4 changed files with 67 additions and 9 deletions
+20 -7
View File
@@ -1,6 +1,7 @@
#include <eepp/core/containers.hpp>
#include <eepp/graphics/richtext.hpp>
#include <eepp/ui/blocklayouter.hpp>
#include <eepp/ui/flexlayouter.hpp>
#include <eepp/ui/uihtmltable.hpp>
#include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uirichtext.hpp>
@@ -10,13 +11,21 @@
namespace EE { namespace UI {
static bool isStretchedFlexItem( UIHTMLWidget* widget ) {
static bool isStretchedFlexItemInAxis( UIHTMLWidget* widget, bool horizontalAxis ) {
Node* parent = widget->getParent();
if ( !parent || !parent->isWidget() || !parent->isType( UI_TYPE_HTML_WIDGET ) )
return false;
UIHTMLWidget* parentHtml = parent->asType<UIHTMLWidget>();
if ( !parentHtml->isFlex() )
return false;
if ( static_cast<FlexLayouter*>( parentHtml->getLayouter() )->isMeasuringItems() )
return false;
CSSFlexDirection direction = parentHtml->getFlexDirection();
bool crossAxisIsHorizontal =
direction == CSSFlexDirection::Column || direction == CSSFlexDirection::ColumnReverse;
if ( horizontalAxis != crossAxisIsHorizontal )
return false;
CSSAlignSelf alignSelf = widget->getAlignSelf();
if ( alignSelf == CSSAlignSelf::Stretch )
@@ -179,9 +188,11 @@ void BlockLayouter::updateLayout() {
}
}
if ( totW != mContainer->getPixelsSize().getWidth() ||
( mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent &&
!isStretchedFlexItem( widget ) ) )
bool preserveStretchedWidth = mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent &&
isStretchedFlexItemInAxis( widget, true );
if ( !preserveStretchedWidth &&
( totW != mContainer->getPixelsSize().getWidth() ||
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) )
mContainer->setInternalPixelsWidth( totW );
Float totH = mContainer->getPixelsSize().getHeight();
@@ -207,9 +218,11 @@ void BlockLayouter::updateLayout() {
}
}
if ( totH != mContainer->getPixelsSize().getHeight() ||
( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent &&
!isStretchedFlexItem( widget ) ) )
bool preserveStretchedHeight = mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent &&
isStretchedFlexItemInAxis( widget, false );
if ( !preserveStretchedHeight &&
( totH != mContainer->getPixelsSize().getHeight() ||
mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) )
mContainer->setInternalPixelsHeight( totH );
mContainer->endAttributesTransaction();
+4 -2
View File
@@ -1223,8 +1223,8 @@ void FlexLayouter::updateLayout() {
return;
RichText* richText = widget->isType( UI_TYPE_RICHTEXT )
? widget->asType<UIRichText>()->getRichTextPtr()
: nullptr;
? widget->asType<UIRichText>()->getRichTextPtr()
: nullptr;
bool preserveFloatConstrainedBFCWidth =
widget->establishesBlockFormattingContext() &&
widget->getLayoutWidthPolicy() == SizePolicy::MatchParent && richText != nullptr &&
@@ -1387,6 +1387,7 @@ void FlexLayouter::updateLayout() {
bool indefiniteMainSize = ( mainAxis.horizontal && widthPolicy == SizePolicy::WrapContent ) ||
( !mainAxis.horizontal && heightPolicy == SizePolicy::WrapContent );
mMeasuringItems = true;
measureFlexItems( mainAxis, crossAxis, containerCrossSize, containerWidth, containerHeight,
containerPadding, indefiniteMainSize, indefiniteCrossSize );
@@ -1410,6 +1411,7 @@ void FlexLayouter::updateLayout() {
alignMainAxis( line, containerMainSize, mColumnGap );
resolveCrossSizes( line, crossAxis, mainAxis );
}
mMeasuringItems = false;
alignCrossAxis( lines, containerCrossSize, mRowGap, crossAxis );
+41
View File
@@ -5136,6 +5136,47 @@ UTEST( UIHTML, FlexLiItemsWrapContentWidth ) {
Engine::destroySingleton();
}
UTEST( UIHTML, FlexStretchPreservesAutoCrossSizeAfterChildLayout ) {
Engine::instance()->createWindow(
WindowSettings( 1024, 768, "Flex Stretch Auto Cross Size Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
UISceneNode* sceneNode = init_test_inline_block();
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
<html><head><style>
.container { display: flex; width: 600px; }
.short { width: 200px; padding: 10px; }
.tall { width: 200px; height: 300px; }
</style></head><body>
<div class="container">
<aside class="short"><p>Short content</p></aside>
<main class="tall"></main>
</div>
</body></html>
)html" ) );
sceneNode->update( Seconds( 1 ) );
sceneNode->updateDirtyLayouts();
auto* containerNode = sceneNode->getRoot()->findByClass( "container" );
auto* shortItemNode = sceneNode->getRoot()->findByClass( "short" );
auto* tallItemNode = sceneNode->getRoot()->findByClass( "tall" );
ASSERT_TRUE( containerNode != nullptr );
ASSERT_TRUE( shortItemNode != nullptr );
ASSERT_TRUE( tallItemNode != nullptr );
auto* container = containerNode->asType<UIWidget>();
auto* shortItem = shortItemNode->asType<UIWidget>();
auto* tallItem = tallItemNode->asType<UIWidget>();
EXPECT_NEAR( container->getPixelsSize().getHeight(), tallItem->getPixelsSize().getHeight(),
1.f );
EXPECT_NEAR( shortItem->getPixelsSize().getHeight(), tallItem->getPixelsSize().getHeight(),
1.f );
Engine::destroySingleton();
}
UTEST( UIHTML, ImagePercentageWidthRespectsParentMaxWidth ) {
auto win = Engine::instance()->createWindow(
WindowSettings( 1024, 768, "img pct width respects parent max-width", WindowStyle::Default,