From ddfd59e1674c20b10fcf94d098e5857ec0f66dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 29 May 2026 21:20:56 -0300 Subject: [PATCH] More flex improvements. --- bin/unit_tests/assets/html/flex_li_width.html | 23 ++ include/eepp/ui/flexlayouter.hpp | 17 +- src/eepp/ui/blocklayouter.cpp | 2 +- src/eepp/ui/flexlayouter.cpp | 227 +++++++++++++----- src/tests/unit_tests/uihtml_tests.cpp | 54 +++++ 5 files changed, 258 insertions(+), 65 deletions(-) create mode 100644 bin/unit_tests/assets/html/flex_li_width.html diff --git a/bin/unit_tests/assets/html/flex_li_width.html b/bin/unit_tests/assets/html/flex_li_width.html new file mode 100644 index 000000000..674c73b50 --- /dev/null +++ b/bin/unit_tests/assets/html/flex_li_width.html @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/include/eepp/ui/flexlayouter.hpp b/include/eepp/ui/flexlayouter.hpp index 561ae4c28..53b4253b6 100644 --- a/include/eepp/ui/flexlayouter.hpp +++ b/include/eepp/ui/flexlayouter.hpp @@ -33,6 +33,15 @@ class EE_API FlexLayouter : public UILayouter { Float mainPos; Float crossPos; + // Margins along the flex axes + Float marginMainStart{ 0.f }; + Float marginMainEnd{ 0.f }; + Float marginCrossStart{ 0.f }; + Float marginCrossEnd{ 0.f }; + + Float minMainSize{ 0.f }; + Float maxMainSize{ std::numeric_limits::max() }; + FlexItem() : widget( nullptr ), order( 0 ), @@ -76,8 +85,7 @@ class EE_API FlexLayouter : public UILayouter { void readContainerStyle( CSSFlexDirection& direction, CSSFlexWrap& wrap, CSSJustifyContent& justify, CSSAlignItems& alignItems, - CSSAlignContent& alignContent, Float& columnGap, - Float& rowGap ); + CSSAlignContent& alignContent, Float& columnGap, Float& rowGap ); void readItemStyle( UIWidget* child, FlexItem& item ); @@ -100,8 +108,7 @@ class EE_API FlexLayouter : public UILayouter { void alignMainAxis( FlexLine& line, const Float containerMainSize, const Float columnGap ); - void resolveCrossSizes( FlexLine& line, const Axis& crossAxis, - const Float containerCrossSize ); + void resolveCrossSizes( FlexLine& line, const Axis& crossAxis, const Axis& mainAxis ); void alignCrossAxis( const std::vector& lines, const Float containerCrossSize, const Float rowGap, const Axis& crossAxis ); @@ -114,7 +121,7 @@ class EE_API FlexLayouter : public UILayouter { void measureFlexItems( const Axis& mainAxis, const Axis& crossAxis, const Float containerCrossSize, const Float containerWidth, const Float containerHeight, const Rectf& containerPadding, - bool indefiniteMainSize ); + bool indefiniteMainSize, bool indefiniteCrossSize ); std::vector mItems; CSSFlexDirection mDirection{ CSSFlexDirection::Row }; diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 90e74f221..b392cb3f6 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -88,7 +88,7 @@ void BlockLayouter::updateLayout() { bool preserveFloatConstrainedBFCWidth = widget->establishesBlockFormattingContext() && !rt->getExternalFloatExclusions().empty(); - if ( !preserveFloatConstrainedBFCWidth ) + if ( !preserveFloatConstrainedBFCWidth && !parentIsFlex ) setMatchParentIfNeededVerticalGrowth(); const StyleSheetProperty* prop = nullptr; diff --git a/src/eepp/ui/flexlayouter.cpp b/src/eepp/ui/flexlayouter.cpp index 8b998f9d7..d7c8cf39a 100644 --- a/src/eepp/ui/flexlayouter.cpp +++ b/src/eepp/ui/flexlayouter.cpp @@ -166,13 +166,16 @@ Float FlexLayouter::getItemCrossSize( UIWidget* child, const Axis& crossAxis ) c void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis, const Float containerCrossSize, const Float containerWidth, const Float containerHeight, const Rectf& containerPadding, - bool indefiniteMainSize ) { + bool indefiniteMainSize, bool indefiniteCrossSize ) { for ( auto& item : mItems ) { Float tentativeCrossSize = containerCrossSize; - if ( tentativeCrossSize <= 0.f && crossAxis.horizontal ) + if ( indefiniteCrossSize ) { + tentativeCrossSize = 0.f; + } else if ( tentativeCrossSize <= 0.f && crossAxis.horizontal ) { tentativeCrossSize = containerWidth - containerPadding.Left - containerPadding.Right; - if ( tentativeCrossSize <= 0.f && !crossAxis.horizontal ) + } else if ( tentativeCrossSize <= 0.f && !crossAxis.horizontal ) { tentativeCrossSize = containerHeight - containerPadding.Top - containerPadding.Bottom; + } if ( tentativeCrossSize < 0.f ) tentativeCrossSize = 0.f; @@ -207,6 +210,22 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis item.widget->setLayoutHeightPolicy( oldMainPolicy ); } + // Resolve margins along the flex axes. + // Auto margins on the main axis are handled by the flex algorithm + // (CSS Flexbox §8.1), so we treat them as 0 here. + Rectf margin = item.widget->getLayoutPixelsMargin(); + if ( mainAxis.horizontal ) { + item.marginMainStart = item.widget->hasLayoutMarginLeftAuto() ? 0.f : margin.Left; + item.marginMainEnd = item.widget->hasLayoutMarginRightAuto() ? 0.f : margin.Right; + item.marginCrossStart = margin.Top; + item.marginCrossEnd = margin.Bottom; + } else { + item.marginMainStart = item.widget->hasLayoutMarginTopAuto() ? 0.f : margin.Top; + item.marginMainEnd = item.widget->hasLayoutMarginBottomAuto() ? 0.f : margin.Bottom; + item.marginCrossStart = margin.Left; + item.marginCrossEnd = margin.Right; + } + item.targetMainSize = resolveFlexBasis( item.widget, mDirection, item.flexBasisValue, item.flexBasisAuto, mainAxis ); @@ -227,6 +246,38 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis if ( hprop ) item.targetMainSize = item.widget->lengthFromValue( *hprop ); } + + // Compute min/max main size (CSS §4.5: min-width/min-height default is auto for flex items) + if ( mainAxis.horizontal ) { + if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) && + item.widget->asType()->getLayouter() ) + item.minMainSize = + item.widget->asType()->getLayouter()->getMinIntrinsicWidth(); + else + item.minMainSize = item.widget->getPixelsSize().getWidth(); + if ( item.minMainSize < 0.f ) + item.minMainSize = 0.f; + + if ( item.widget->getUIStyle() ) { + const auto* maxW = item.widget->getUIStyle()->getProperty( PropertyId::MaxWidth ); + if ( maxW ) + item.maxMainSize = item.widget->lengthFromValue( *maxW ); + else + item.maxMainSize = std::numeric_limits::max(); + } + } else { + item.minMainSize = item.widget->getPixelsSize().getHeight(); + if ( item.minMainSize < 0.f ) + item.minMainSize = 0.f; + + if ( item.widget->getUIStyle() ) { + const auto* maxH = item.widget->getUIStyle()->getProperty( PropertyId::MaxHeight ); + if ( maxH ) + item.maxMainSize = item.widget->lengthFromValue( *maxH ); + else + item.maxMainSize = std::numeric_limits::max(); + } + } } } @@ -245,13 +296,15 @@ void FlexLayouter::generateFlexLines( std::vector& lines std::vector nextRemaining; for ( size_t idx : remaining ) { - Float itemSize = mItems[idx].targetMainSize; + const auto& item = mItems[idx]; + Float itemOuterSize = item.targetMainSize + item.marginMainStart + item.marginMainEnd; Float gap = line.itemIndices.empty() ? 0.f : columnGap; - if ( mWrap == CSSFlexWrap::NoWrap || ( usedMain + gap + itemSize <= containerMainSize && - usedMain + gap + itemSize >= 0.f ) ) { + if ( mWrap == CSSFlexWrap::NoWrap || + ( usedMain + gap + itemOuterSize <= containerMainSize && + usedMain + gap + itemOuterSize >= 0.f ) ) { line.itemIndices.push_back( idx ); - usedMain += gap + itemSize; + usedMain += gap + itemOuterSize; } else { nextRemaining.push_back( idx ); } @@ -296,9 +349,10 @@ void FlexLayouter::resolveFlexibleLengths( FlexLine& line, const Float container Float totalFlexShrink = 0.f; for ( size_t idx : line.itemIndices ) { - totalFlexBasis += mItems[idx].targetMainSize; - totalFlexGrow += mItems[idx].flexGrow; - totalFlexShrink += mItems[idx].flexShrink; + const auto& item = mItems[idx]; + totalFlexBasis += item.targetMainSize + item.marginMainStart + item.marginMainEnd; + totalFlexGrow += item.flexGrow; + totalFlexShrink += item.flexShrink; } size_t itemCount = line.itemIndices.size(); @@ -353,6 +407,15 @@ void FlexLayouter::resolveFlexibleLengths( FlexLine& line, const Float container } } } + + // Clamp each item by its min/max main size (CSS Flexbox §9.7 step 6) + for ( size_t idx : line.itemIndices ) { + auto& item = mItems[idx]; + if ( item.targetMainSize < item.minMainSize ) + item.targetMainSize = item.minMainSize; + if ( item.targetMainSize > item.maxMainSize ) + item.targetMainSize = item.maxMainSize; + } } void FlexLayouter::alignMainAxis( FlexLine& line, const Float containerMainSize, @@ -362,13 +425,13 @@ void FlexLayouter::alignMainAxis( FlexLine& line, const Float containerMainSize, size_t itemCount = line.itemIndices.size(); Float totalItemMain = 0.f; - for ( size_t idx : line.itemIndices ) - totalItemMain += mItems[idx].targetMainSize; + for ( size_t idx : line.itemIndices ) { + const auto& item = mItems[idx]; + totalItemMain += item.targetMainSize + item.marginMainStart + item.marginMainEnd; + } Float totalGap = ( itemCount > 1 ) ? (Float)( itemCount - 1 ) * columnGap : 0.f; Float freeSpaceMain = containerMainSize - totalItemMain - totalGap; - if ( freeSpaceMain < 0.f ) - freeSpaceMain = 0.f; Float mainOffset = 0.f; Float spacing = columnGap; @@ -381,19 +444,19 @@ void FlexLayouter::alignMainAxis( FlexLine& line, const Float containerMainSize, mainOffset = freeSpaceMain * 0.5f; break; case CSSJustifyContent::SpaceBetween: - if ( itemCount > 1 ) + if ( freeSpaceMain > 0.f && itemCount > 1 ) spacing = columnGap + freeSpaceMain / (Float)( itemCount - 1 ); else mainOffset = 0.f; break; case CSSJustifyContent::SpaceAround: - if ( itemCount > 0 ) { + if ( freeSpaceMain > 0.f && itemCount > 0 ) { spacing = columnGap + freeSpaceMain / (Float)itemCount; mainOffset = spacing * 0.5f; } break; case CSSJustifyContent::SpaceEvenly: - if ( itemCount > 0 ) { + if ( freeSpaceMain > 0.f && itemCount > 0 ) { Float slot = columnGap + freeSpaceMain / (Float)( itemCount + 1 ); mainOffset = slot; spacing = slot; @@ -407,32 +470,40 @@ void FlexLayouter::alignMainAxis( FlexLine& line, const Float containerMainSize, Float pos = mainOffset; for ( size_t idx : line.itemIndices ) { - mItems[idx].mainPos = pos; - pos += mItems[idx].targetMainSize + spacing; + const auto& item = mItems[idx]; + mItems[idx].mainPos = pos + item.marginMainStart; + pos += item.targetMainSize + item.marginMainStart + item.marginMainEnd + spacing; } } void FlexLayouter::resolveCrossSizes( FlexLine& line, const Axis& crossAxis, - const Float containerCrossSize ) { + const Axis& mainAxis ) { if ( line.itemIndices.empty() ) { line.crossSize = 0.f; return; } + // Set each item's main-axis size to its resolved targetMainSize so that + // getItemCrossSize reflects the correct cross size for the final width. + for ( size_t idx : line.itemIndices ) { + auto& item = mItems[idx]; + if ( mainAxis.horizontal ) + item.widget->setInternalPixelsWidth( item.targetMainSize ); + else + item.widget->setInternalPixelsHeight( item.targetMainSize ); + if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) ) { + auto* htmlWidget = item.widget->asType(); + if ( htmlWidget->getLayouter() && htmlWidget->getLayouter() != this ) + htmlWidget->updateLayout(); + } + } + Float maxCross = 0.f; for ( size_t idx : line.itemIndices ) { auto& item = mItems[idx]; - CSSAlignSelf resolved = resolveAlignSelf( item.alignSelf, mAlignItems ); - bool canStretch = crossAxis.horizontal - ? ( item.widget->getLayoutWidthPolicy() != SizePolicy::Fixed ) - : ( item.widget->getLayoutHeightPolicy() != SizePolicy::Fixed ); - if ( resolved == CSSAlignSelf::Stretch && canStretch && containerCrossSize > 0.f ) { - item.crossSize = containerCrossSize; - } else { - item.crossSize = getItemCrossSize( item.widget, crossAxis ); - } - item.outerCrossSize = item.crossSize; - maxCross = eemax( maxCross, item.crossSize ); + item.crossSize = getItemCrossSize( item.widget, crossAxis ); + item.outerCrossSize = item.crossSize + item.marginCrossStart + item.marginCrossEnd; + maxCross = eemax( maxCross, item.outerCrossSize ); } line.crossSize = maxCross; } @@ -456,6 +527,7 @@ void FlexLayouter::alignCrossAxis( const std::vector& lines, Float lineGap = rowGap; if ( lineCount > 1 ) { + Float fsc = freeSpaceCross; switch ( mAlignContent ) { case CSSAlignContent::FlexEnd: crossOffset = freeSpaceCross; @@ -464,20 +536,24 @@ void FlexLayouter::alignCrossAxis( const std::vector& lines, crossOffset = freeSpaceCross * 0.5f; break; case CSSAlignContent::SpaceBetween: - if ( lineCount > 1 ) - lineGap = freeSpaceCross / (Float)( lineCount - 1 ); + if ( fsc > 0.f && lineCount > 1 ) + lineGap = fsc / (Float)( lineCount - 1 ); break; case CSSAlignContent::SpaceAround: - lineGap = freeSpaceCross / (Float)lineCount; - crossOffset = lineGap * 0.5f; + if ( fsc > 0.f ) { + lineGap = fsc / (Float)lineCount; + crossOffset = lineGap * 0.5f; + } break; case CSSAlignContent::SpaceEvenly: - lineGap = freeSpaceCross / (Float)( lineCount + 1 ); - crossOffset = lineGap; + if ( fsc > 0.f ) { + lineGap = fsc / (Float)( lineCount + 1 ); + crossOffset = lineGap; + } break; case CSSAlignContent::Stretch: - if ( lineCount > 0 ) - addPerLine = freeSpaceCross / (Float)lineCount; + if ( fsc > 0.f && lineCount > 0 ) + addPerLine = fsc / (Float)lineCount; break; case CSSAlignContent::FlexStart: default: @@ -491,6 +567,8 @@ void FlexLayouter::alignCrossAxis( const std::vector& lines, line.crossPos = pos; Float lineCrossSize = line.crossSize + addPerLine; + if ( lineCrossSize < line.crossSize ) + lineCrossSize = line.crossSize; if ( lines.size() == 1 && containerCrossSize > 0.f ) lineCrossSize = containerCrossSize; if ( lineCrossSize > containerCrossSize && containerCrossSize > 0.f ) @@ -500,30 +578,31 @@ void FlexLayouter::alignCrossAxis( const std::vector& lines, auto& item = mItems[idx]; CSSAlignSelf resolved = resolveAlignSelf( item.alignSelf, mAlignItems ); - Float itemFreeSpace = lineCrossSize - item.crossSize; + Float itemFreeSpace = lineCrossSize - item.outerCrossSize; switch ( resolved ) { case CSSAlignSelf::FlexStart: - item.crossPos = pos; + item.crossPos = pos + item.marginCrossStart; break; case CSSAlignSelf::FlexEnd: - item.crossPos = pos + itemFreeSpace; + item.crossPos = pos + itemFreeSpace + item.marginCrossStart; break; case CSSAlignSelf::Center: - item.crossPos = pos + itemFreeSpace * 0.5f; + item.crossPos = pos + itemFreeSpace * 0.5f + item.marginCrossStart; break; case CSSAlignSelf::Stretch: - item.crossPos = pos; + item.crossPos = pos + item.marginCrossStart; if ( !( crossAxis.horizontal ? ( item.widget->getLayoutWidthPolicy() == SizePolicy::Fixed ) : ( item.widget->getLayoutHeightPolicy() == SizePolicy::Fixed ) ) ) - item.crossSize = lineCrossSize; + item.crossSize = + lineCrossSize - item.marginCrossStart - item.marginCrossEnd; break; case CSSAlignSelf::Baseline: - item.crossPos = pos; + item.crossPos = pos + item.marginCrossStart; break; case CSSAlignSelf::Auto: default: - item.crossPos = pos; + item.crossPos = pos + item.marginCrossStart; break; } } @@ -554,8 +633,10 @@ void FlexLayouter::applyLayout( const std::vector& lines, const Axis& widgetY = containerPadding.Top + item.crossPos; widgetW = item.targetMainSize; widgetH = item.crossSize; - contentW = eemax( contentW, widgetX + widgetW - containerPadding.Left ); - contentH = eemax( contentH, widgetY + widgetH - containerPadding.Top ); + contentW = eemax( contentW, + widgetX + widgetW + item.marginMainEnd - containerPadding.Left ); + contentH = eemax( contentH, + widgetY + widgetH + item.marginCrossEnd - containerPadding.Top ); } else { if ( mainAxis.reversed ) { widgetY = containerHeight - containerPadding.Bottom - item.mainPos - @@ -566,8 +647,10 @@ void FlexLayouter::applyLayout( const std::vector& lines, const Axis& widgetX = containerPadding.Left + item.crossPos; widgetW = item.crossSize; widgetH = item.targetMainSize; - contentW = eemax( contentW, widgetX + widgetW - containerPadding.Left ); - contentH = eemax( contentH, widgetY + widgetH - containerPadding.Top ); + contentW = eemax( contentW, + widgetX + widgetW + item.marginCrossEnd - containerPadding.Left ); + contentH = eemax( contentH, + widgetY + widgetH + item.marginMainEnd - containerPadding.Top ); } item.widget->setPixelsPosition( widgetX, widgetY ); @@ -669,6 +752,16 @@ void FlexLayouter::updateLayout() { if ( containerCrossSize < 0.f ) containerCrossSize = 0.f; + // Detect indefinite cross size: WrapContent in the cross axis direction + bool indefiniteCrossSize = ( crossAxis.horizontal && widthPolicy == SizePolicy::WrapContent ) || + ( !crossAxis.horizontal && heightPolicy == SizePolicy::WrapContent ); + + // When cross size is indefinite, zero out containerCrossSize so that + // the flex algorithm sizes lines to content rather than using a stale + // container size from a previous layout pass. + if ( indefiniteCrossSize ) + containerCrossSize = 0.f; + collectFlexItems( mItems ); if ( mItems.empty() ) { @@ -735,15 +828,15 @@ void FlexLayouter::updateLayout() { ( !mainAxis.horizontal && heightPolicy == SizePolicy::WrapContent ); measureFlexItems( mainAxis, crossAxis, containerCrossSize, containerWidth, containerHeight, - containerPadding, indefiniteMainSize ); + containerPadding, indefiniteMainSize, indefiniteCrossSize ); std::vector lines; if ( indefiniteMainSize ) { - // Compute container main size from item content sizes (CSS §9.3) + // Compute container main size from item outer sizes (CSS §9.3) Float naturalMainSize = 0.f; for ( auto& item : mItems ) - naturalMainSize += item.targetMainSize; + naturalMainSize += item.targetMainSize + item.marginMainStart + item.marginMainEnd; if ( mItems.size() > 1 ) naturalMainSize += (Float)( mItems.size() - 1 ) * mColumnGap; containerMainSize = naturalMainSize; @@ -755,7 +848,7 @@ void FlexLayouter::updateLayout() { if ( !indefiniteMainSize ) resolveFlexibleLengths( line, containerMainSize, mColumnGap ); alignMainAxis( line, containerMainSize, mColumnGap ); - resolveCrossSizes( line, crossAxis, containerCrossSize ); + resolveCrossSizes( line, crossAxis, mainAxis ); } alignCrossAxis( lines, containerCrossSize, mRowGap, crossAxis ); @@ -798,6 +891,19 @@ void FlexLayouter::computeIntrinsicWidths() { for ( auto& item : items ) { item.targetMainSize = resolveFlexBasis( item.widget, mDirection, item.flexBasisValue, item.flexBasisAuto, mainAxis ); + + Rectf margin = item.widget->getLayoutPixelsMargin(); + if ( mainAxis.horizontal ) { + item.marginMainStart = item.widget->hasLayoutMarginLeftAuto() ? 0.f : margin.Left; + item.marginMainEnd = item.widget->hasLayoutMarginRightAuto() ? 0.f : margin.Right; + item.marginCrossStart = margin.Top; + item.marginCrossEnd = margin.Bottom; + } else { + item.marginMainStart = item.widget->hasLayoutMarginTopAuto() ? 0.f : margin.Top; + item.marginMainEnd = item.widget->hasLayoutMarginBottomAuto() ? 0.f : margin.Bottom; + item.marginCrossStart = margin.Left; + item.marginCrossEnd = margin.Right; + } } const Rectf containerPadding = mContainer->getPixelsContentOffset(); @@ -805,7 +911,7 @@ void FlexLayouter::computeIntrinsicWidths() { if ( mWrap == CSSFlexWrap::NoWrap ) { Float maxContentMain = 0.f; for ( auto& item : items ) - maxContentMain += item.targetMainSize; + maxContentMain += item.targetMainSize + item.marginMainStart + item.marginMainEnd; if ( items.size() > 1 ) maxContentMain += (Float)( items.size() - 1 ) * mColumnGap; @@ -815,7 +921,8 @@ void FlexLayouter::computeIntrinsicWidths() { } else { Float maxItemWidth = 0.f; for ( auto& item : items ) - maxItemWidth = eemax( maxItemWidth, item.targetMainSize ); + maxItemWidth = eemax( maxItemWidth, item.targetMainSize + item.marginCrossStart + + item.marginCrossEnd ); mMaxIntrinsicWidth = maxItemWidth + containerPadding.Left + containerPadding.Right; mMinIntrinsicWidth = containerPadding.Left + containerPadding.Right; } @@ -824,7 +931,8 @@ void FlexLayouter::computeIntrinsicWidths() { Float maxLineWidth = 0.f; Float lineSum = 0.f; for ( auto& item : items ) { - lineSum += ( lineSum > 0.f ? mColumnGap : 0.f ) + item.targetMainSize; + lineSum += ( lineSum > 0.f ? mColumnGap : 0.f ) + item.targetMainSize + + item.marginMainStart + item.marginMainEnd; } maxLineWidth = lineSum; mMaxIntrinsicWidth = maxLineWidth + containerPadding.Left + containerPadding.Right; @@ -832,7 +940,8 @@ void FlexLayouter::computeIntrinsicWidths() { } else { Float maxItemWidth = 0.f; for ( auto& item : items ) - maxItemWidth = eemax( maxItemWidth, item.targetMainSize ); + maxItemWidth = eemax( maxItemWidth, item.targetMainSize + item.marginCrossStart + + item.marginCrossEnd ); mMaxIntrinsicWidth = maxItemWidth + containerPadding.Left + containerPadding.Right; mMinIntrinsicWidth = containerPadding.Left + containerPadding.Right; } diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 1b0dca233..40058db2e 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -3366,3 +3366,57 @@ UTEST( UIHTML, FlexAnchorInFlexNavVisible ) { Engine::destroySingleton(); } + +UTEST( UIHTML, FlexLiItemsWrapContentWidth ) { + Engine::instance()->createWindow( WindowSettings( 1024, 768, "Flex LI Width Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + + UISceneNode* sceneNode = init_test_inline_block(); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + + std::string html; + FileSystem::fileGet( "assets/html/flex_li_width.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + + sceneNode->update( Seconds( 1 ) ); + sceneNode->updateDirtyLayouts(); + + auto menuContainer = sceneNode->getRoot()->findByClass( "menu-container" ); + ASSERT_TRUE( menuContainer != nullptr ); + auto menuWidget = menuContainer->asType(); + + // The menu container is a flex row; its
  • children should wrap to + // their content width, not span the full container width. + Float containerWidth = menuWidget->getPixelsSize().getWidth(); + Float containerPadding = + menuWidget->getPixelsPadding().Left + menuWidget->getPixelsPadding().Right; + Float contentWidth = containerWidth - containerPadding; + + Float totalLiWidth = 0.f; + Uint32 liCount = 0; + for ( Uint32 i = 0; i < menuWidget->getChildCount(); ++i ) { + auto c = menuWidget->getChildAt( i ); + if ( !c->isWidget() ) + continue; + auto cw = c->asType(); + if ( cw->getElementTag() != "li" ) + continue; + + // Each
  • should be narrower than the container content width + EXPECT_LT( cw->getPixelsSize().getWidth(), contentWidth ); + + // Accumulate total width (including margin) + Rectf margin = cw->getLayoutPixelsMargin(); + totalLiWidth += cw->getPixelsSize().getWidth() + margin.Left + margin.Right; + liCount++; + } + + ASSERT_GT( liCount, (Uint32)0 ); + // With 9 items and horizontal margins, the total should still fit without + // each item spanning the full container. + EXPECT_LT( totalLiWidth, contentWidth * (Float)liCount ); + + Engine::destroySingleton(); +}