diff --git a/bin/unit_tests/assets/html/css_gradient_tests.html b/bin/unit_tests/assets/html/css_gradient_tests.html new file mode 100644 index 000000000..12f421ed7 --- /dev/null +++ b/bin/unit_tests/assets/html/css_gradient_tests.html @@ -0,0 +1,283 @@ + + + + + +CSS Gradient Function Tests + + + +

CSS Gradient Function Tests

+ +
+

linear-gradient()

+
+
Default directionlinear-gradient(red, blue)
+
Keyword directionlinear-gradient(to right, red, yellow, blue)
+
Corner directionlinear-gradient(to bottom right, red, yellow, blue)
+
Angle in degreeslinear-gradient(45deg, red, yellow, lime, cyan, blue)
+
Negative anglelinear-gradient(-30deg, #ff3366, #6633ff)
+
Angle in turnslinear-gradient(0.25turn, red, orange, yellow, green, blue)
+
Percentage stopslinear-gradient(to right, red 0%, yellow 25%, green 50%, cyan 75%, blue 100%)
+
Length stopslinear-gradient(to right, red 0px, yellow 45px, green 90px, blue 150px)
+
Hard color bands / multi-position stopslinear-gradient(to right, red 0 25%, white 25% 50%, blue 50% 75%, black 75% 100%)
+
Out-of-order / overlapping stopslinear-gradient(to right, red 40%, yellow 20%, blue 80%)
+
Alpha over fallback colorlinear-gradient(to right, rgba(255,0,0,1), rgba(255,0,0,0)), #1e90ff
+
Multiple backgrounds + background-sizelinear-gradient(...), linear-gradient(...); background-size: 40px 40px, auto
+
+
+ +
+

repeating-linear-gradient()

+
+
Basic repeating bandsrepeating-linear-gradient(red 0 10px, blue 10px 20px)
+
Diagonal repeating bandsrepeating-linear-gradient(45deg, red 0 8px, yellow 8px 16px, blue 16px 24px)
+
Keyword directionrepeating-linear-gradient(to right, #111 0 10px, #eee 10px 20px)
+
Very thin linesrepeating-linear-gradient(to bottom, black 0 1px, white 1px 2px)
+
Soft repeating transitionsrepeating-linear-gradient(90deg, red 0px, yellow 18px, blue 36px)
+
Mixed hard/soft rangesrepeating-linear-gradient(135deg, #ff3366 0 12px, #ffd166 12px 18px, #06d6a0 18px 36px)
+
Transparent stripes over colorrepeating-linear-gradient(45deg, rgba(...), transparent), #304060
+
Short repeat periodrepeating-linear-gradient(30deg, red 0 3px, blue 3px 6px)
+
Long repeat periodrepeating-linear-gradient(30deg, red 0 30px, yellow 30px 60px, blue 60px 90px)
+
Repeating layer + background-sizerepeating-linear-gradient(...), linear-gradient(...); background-size: 48px 48px, auto
+
+
+ +
+

radial-gradient()

+
+
Default radial gradientradial-gradient(red, blue)
+
Circle shaperadial-gradient(circle, red, yellow, blue)
+
Ellipse shaperadial-gradient(ellipse, red, yellow, blue)
+
Explicit center positionradial-gradient(circle at center, red 0%, yellow 35%, blue 100%)
+
Position at cornerradial-gradient(circle at top left, red, yellow, blue)
+
Position using lengthsradial-gradient(circle at 40px 70px, red, yellow 35%, blue)
+
closest-side sizeradial-gradient(circle closest-side at 70% 35%, red, yellow, blue)
+
farthest-side sizeradial-gradient(circle farthest-side at 70% 35%, red, yellow, blue)
+
closest-corner sizeradial-gradient(circle closest-corner at 70% 35%, red, yellow, blue)
+
farthest-corner sizeradial-gradient(circle farthest-corner at 70% 35%, red, yellow, blue)
+
Explicit ellipse radiiradial-gradient(80px 45px at 50% 50%, red, yellow, blue)
+
Hard radial bandsradial-gradient(circle, red 0 25%, white 25% 50%, blue 50% 75%, black 75% 100%)
+
Transparent fade over colorradial-gradient(circle at center, rgba(...), rgba(...,0) 55%), #1e5a7a
+
Multiple radial layersradial-gradient(...), radial-gradient(...), linear-gradient(...)
+
Radial tile using background-sizeradial-gradient(...), #222; background-size: 64px 64px
+
+
+ + diff --git a/src/eepp/ui/gridlayouter.cpp b/src/eepp/ui/gridlayouter.cpp index 9ea338749..e1a7da035 100644 --- a/src/eepp/ui/gridlayouter.cpp +++ b/src/eepp/ui/gridlayouter.cpp @@ -466,6 +466,19 @@ void GridTrackParser::expandAutoRepeat( GridTrackList& list, Float availableSize // ── GridLayouter ── +static void resolveAutoRepeatLengths( GridTrackList& list, UIWidget* container, + CSS::PropertyRelativeTarget relativeTarget ) { + for ( auto& track : list.autoRepeatTemplate ) { + if ( track.size.min.type == GridTrackBreadthType::Length ) + track.size.min.value = container->lengthFromValue( track.size.min.raw, relativeTarget, + track.size.min.value ); + if ( track.size.max.type == GridTrackBreadthType::Length || + track.size.max.type == GridTrackBreadthType::FitContent ) + track.size.max.value = container->lengthFromValue( track.size.max.raw, relativeTarget, + track.size.max.value ); + } +} + void GridLayouter::readContainerStyle() { UIHTMLWidget* grid = mContainer->isType( UI_TYPE_HTML_WIDGET ) ? mContainer->asType() : nullptr; @@ -492,8 +505,11 @@ void GridLayouter::readContainerStyle() { Float contentW = mContainer->getPixelsSize().getWidth() - mContainer->getPixelsContentOffset().Left - mContainer->getPixelsContentOffset().Right; - if ( contentW > 0.f ) + if ( contentW > 0.f ) { + resolveAutoRepeatLengths( colList, mContainer, + CSS::PropertyRelativeTarget::ContainingBlockWidth ); GridTrackParser::expandAutoRepeat( colList, contentW, mColumnGap ); + } } mColumns.clear(); mColLineNames.clear(); @@ -518,8 +534,11 @@ void GridLayouter::readContainerStyle() { Float contentH = mContainer->getPixelsSize().getHeight() - mContainer->getPixelsContentOffset().Top - mContainer->getPixelsContentOffset().Bottom; - if ( contentH > 0.f ) + if ( contentH > 0.f ) { + resolveAutoRepeatLengths( rowList, mContainer, + CSS::PropertyRelativeTarget::ContainingBlockHeight ); GridTrackParser::expandAutoRepeat( rowList, contentH, mRowGap ); + } } mRows.clear(); mRowLineNames.clear(); @@ -1542,6 +1561,8 @@ void GridLayouter::computeIntrinsicWidths() { // Expand auto-repeat tracks (use a large available size as heuristic) if ( colList.hasAutoRepeat && !colList.autoRepeatTemplate.empty() ) { Float intrinsicExpandSize = 10000.f; // large enough to get a meaningful count + resolveAutoRepeatLengths( colList, mContainer, + CSS::PropertyRelativeTarget::ContainingBlockWidth ); GridTrackParser::expandAutoRepeat( colList, intrinsicExpandSize, gap ); } diff --git a/src/tests/unit_tests/uihtml_grid_test.cpp b/src/tests/unit_tests/uihtml_grid_test.cpp index 68b544333..01832d7cb 100644 --- a/src/tests/unit_tests/uihtml_grid_test.cpp +++ b/src/tests/unit_tests/uihtml_grid_test.cpp @@ -1928,3 +1928,71 @@ UTEST( GridContainer, newsblurReducedGrid ) { Engine::destroySingleton(); } + +UTEST( GridContainer, gradientFixtureAutoFitItemsStayInsideOnResize ) { + Engine::instance()->createWindow( WindowSettings( 1024, 650, "UIHTML Grid Gradient Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings() ); + PixelDensity::setPixelDensity( 2.f ); + init_grid_test(); + UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + + std::string htmlContent; + ASSERT_TRUE( FileSystem::fileGet( "assets/html/css_gradient_tests.html", htmlContent ) ); + sceneNode->loadLayoutFromString( EE::UI::Tools::HTMLFormatter::HTMLtoXML( htmlContent ) ); + + auto assertItemsInsideGrid = [&]() { + auto grids = sceneNode->getRoot()->findAllByClass( "grid" ); + ASSERT_GT( grids.size(), 1u ); + for ( auto* grid : grids ) { + ASSERT_TRUE( grid->getParent()->isWidget() ); + auto* parent = grid->getParent()->asType(); + Float parentContentRight = + parent->getPixelsSize().getWidth() - parent->getPixelsContentOffset().Right; + EXPECT_LE( grid->getPixelsPosition().x + grid->getPixelsSize().getWidth(), + parentContentRight + 0.5f ); + + auto cases = grid->findAllByClass( "case" ); + ASSERT_GT( cases.size(), 1u ); + Float contentWidth = grid->getPixelsSize().getWidth() - + grid->getPixelsContentOffset().Left - + grid->getPixelsContentOffset().Right; + auto* gridLayouter = + static_cast( grid->asType()->getLayouter() ); + ASSERT_TRUE( nullptr != gridLayouter ); + const auto& columns = gridLayouter->getColumns(); + Float gap = + grid->lengthFromValue( "16px", CSS::PropertyRelativeTarget::ContainingBlockWidth ); + Float minTrack = + grid->lengthFromValue( "240px", CSS::PropertyRelativeTarget::ContainingBlockWidth ); + size_t expectedColumns = std::min( + cases.size(), static_cast( ( contentWidth + gap ) / ( minTrack + gap ) ) ); + ASSERT_EQ( expectedColumns, columns.size() ); + Float tracksWidth = static_cast( columns.size() - 1 ) * gap; + for ( const auto& column : columns ) + tracksWidth += column.baseSize; + EXPECT_LE( tracksWidth, contentWidth + 0.5f ); + + Float contentRight = + grid->getPixelsSize().getWidth() - grid->getPixelsContentOffset().Right; + for ( auto* item : cases ) { + Float itemRight = item->getPixelsPosition().x + item->getPixelsSize().getWidth(); + EXPECT_LE( itemRight, contentRight + 0.5f ); + EXPECT_LE( item->getScreenRect().Right, grid->getScreenRect().Right + 0.5f ); + } + } + }; + + for ( const auto& size : + { Sizef( 1200.f, 900.f ), Sizef( 1800.f, 900.f ), Sizef( 1000.f, 900.f ) } ) { + sceneNode->setPixelsSize( size ); + sceneNode->update( Seconds( 1 ) ); + sceneNode->updateDirtyLayouts(); + assertItemsInsideGrid(); + } + + Engine::destroySingleton(); + PixelDensity::setPixelDensity( 1.f ); +}