mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-07-15 07:22:49 +03:00
Grid fixes.
This commit is contained in:
63
bin/unit_tests/assets/html/grid_test.html
Normal file
63
bin/unit_tests/assets/html/grid_test.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style id="css-output">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wrapper {
|
||||
max-width: 940px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.wrapper > div {
|
||||
border: 2px solid rgb(233 171 88);
|
||||
border-radius: 5px;
|
||||
background-color: rgb(233 171 88 / 50%);
|
||||
padding: 1em;
|
||||
color: #d9480f;
|
||||
}
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
grid-auto-rows: minmax(100px, auto);
|
||||
}
|
||||
.one {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
.two {
|
||||
grid-column: 2 / 4;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
.three {
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 5;
|
||||
}
|
||||
.four {
|
||||
grid-column: 3;
|
||||
grid-row: 3;
|
||||
}
|
||||
.five {
|
||||
grid-column: 2;
|
||||
grid-row: 4;
|
||||
}
|
||||
.six {
|
||||
grid-column: 3;
|
||||
grid-row: 4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="one">One</div>
|
||||
<div class="two">Two</div>
|
||||
<div class="three">Three</div>
|
||||
<div class="four">Four</div>
|
||||
<div class="five">Five</div>
|
||||
<div class="six">Six</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -157,6 +157,7 @@ class EE_API GridLayouter : public UILayouter {
|
||||
Float mContainerBaseline{ 0.f };
|
||||
int mMaxColumn{ 0 };
|
||||
int mMaxRow{ 0 };
|
||||
CSSGridAutoFlow mAutoFlow{ CSSGridAutoFlow::Row };
|
||||
bool mAutoFlowDense{ false };
|
||||
bool mColAutoRepeatIsFit{ false };
|
||||
bool mRowAutoRepeatIsFit{ false };
|
||||
|
||||
@@ -255,6 +255,30 @@ static bool isValidTrackBreadthType( const GridTrackBreadth& b ) {
|
||||
b.type == GridTrackBreadthType::Percentage || b.type == GridTrackBreadthType::Flex;
|
||||
}
|
||||
|
||||
static GridTrackSize parseAutoTrackSize( const std::string& value ) {
|
||||
GridTrackSize size;
|
||||
std::string trimmed = String::trim( value );
|
||||
String::toLowerInPlace( trimmed );
|
||||
if ( trimmed.empty() || trimmed == "auto" )
|
||||
return size;
|
||||
|
||||
GridTrackList list = GridTrackParser::parseTrackList( trimmed );
|
||||
if ( list.valid && !list.none && list.tracks.size() == 1 )
|
||||
return list.tracks[0].size;
|
||||
|
||||
GridTrackBreadth b = parseTrackBreadth( trimmed );
|
||||
if ( b.valid ) {
|
||||
size.min = b;
|
||||
size.max = b;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool isFlexibleTrack( const GridTrack& track ) {
|
||||
return track.definition.min.type == GridTrackBreadthType::Flex ||
|
||||
track.definition.max.type == GridTrackBreadthType::Flex;
|
||||
}
|
||||
|
||||
// ── Public API ──
|
||||
|
||||
GridTrackList GridTrackParser::parseTrackList( const std::string& value ) {
|
||||
@@ -453,24 +477,11 @@ void GridLayouter::readContainerStyle() {
|
||||
mRowGap = mContainer->lengthFromValue(
|
||||
grid->getRowGap(), CSS::PropertyRelativeTarget::ContainingBlockHeight, 0.f );
|
||||
mTemplateAreas = GridAreasParser::parseAreas( grid->getGridTemplateAreas() );
|
||||
mAutoFlow = grid->getGridAutoFlow();
|
||||
mAutoFlowDense = grid->getGridAutoFlowDense();
|
||||
|
||||
mAutoColumnSize = GridTrackSize{};
|
||||
mAutoRowSize = GridTrackSize{};
|
||||
std::string autoColsStr = grid->getGridAutoColumns();
|
||||
std::string autoRowsStr = grid->getGridAutoRows();
|
||||
String::toLowerInPlace( autoColsStr );
|
||||
String::toLowerInPlace( autoRowsStr );
|
||||
if ( autoColsStr != "auto" && !autoColsStr.empty() ) {
|
||||
GridTrackBreadth b = parseTrackBreadth( autoColsStr );
|
||||
mAutoColumnSize.min = b;
|
||||
mAutoColumnSize.max = b;
|
||||
}
|
||||
if ( autoRowsStr != "auto" && !autoRowsStr.empty() ) {
|
||||
GridTrackBreadth b = parseTrackBreadth( autoRowsStr );
|
||||
mAutoRowSize.min = b;
|
||||
mAutoRowSize.max = b;
|
||||
}
|
||||
mAutoColumnSize = parseAutoTrackSize( grid->getGridAutoColumns() );
|
||||
mAutoRowSize = parseAutoTrackSize( grid->getGridAutoRows() );
|
||||
|
||||
// Parse and expand column tracks (including auto-repeat)
|
||||
GridTrackList colList = GridTrackParser::parseTrackList( grid->getGridTemplateColumns() );
|
||||
@@ -651,6 +662,12 @@ void GridLayouter::sizeTracksForAxis( bool isColumns ) {
|
||||
Float spannedSize = 0.f;
|
||||
int spanStart = start - 1;
|
||||
int spanEnd = std::min( end - 1, static_cast<int>( tracks.size() ) );
|
||||
bool allSpannedTracksAreFlexible = true;
|
||||
for ( int i = spanStart; i < spanEnd; ++i )
|
||||
allSpannedTracksAreFlexible =
|
||||
allSpannedTracksAreFlexible && isFlexibleTrack( tracks[i] );
|
||||
if ( allSpannedTracksAreFlexible )
|
||||
continue;
|
||||
for ( int i = spanStart; i < spanEnd; ++i )
|
||||
spannedSize += tracks[i].baseSize;
|
||||
spannedSize += static_cast<Float>( span - 1 ) * ( isColumns ? mColumnGap : mRowGap );
|
||||
@@ -746,6 +763,7 @@ void GridLayouter::preSizeItemsForRowSizing() {
|
||||
if ( ce > cs + 1 )
|
||||
cellW += static_cast<Float>( ce - cs - 1 ) * mColumnGap;
|
||||
|
||||
auto oldWidthPolicy = item.widget->getLayoutWidthPolicy();
|
||||
item.widget->setLayoutWidthPolicy( SizePolicy::Fixed );
|
||||
item.widget->setPixelsSize( cellW, item.widget->getPixelsSize().getHeight() );
|
||||
|
||||
@@ -759,6 +777,7 @@ void GridLayouter::preSizeItemsForRowSizing() {
|
||||
childHtml->setLayoutHeightPolicy( oldHP );
|
||||
}
|
||||
}
|
||||
item.widget->setLayoutWidthPolicy( oldWidthPolicy );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,9 +895,9 @@ void GridLayouter::applyLayout() {
|
||||
Float cellH = rowLines[re] - rowLines[rs];
|
||||
// Add internal gaps for multi-span items
|
||||
if ( ce > cs + 1 )
|
||||
cellW += static_cast<Float>( ce - cs - 1 ) * mColumnGap;
|
||||
cellW += static_cast<Float>( ce - cs - 1 ) * colGapX;
|
||||
if ( re > rs + 1 )
|
||||
cellH += static_cast<Float>( re - rs - 1 ) * mRowGap;
|
||||
cellH += static_cast<Float>( re - rs - 1 ) * rowGapY;
|
||||
|
||||
// Determine effective alignment
|
||||
CSSJustifySelf js = item.justifySelf;
|
||||
@@ -947,6 +966,11 @@ void GridLayouter::updateLayout() {
|
||||
|
||||
UIHTMLWidget* grid =
|
||||
mContainer->isType( UI_TYPE_HTML_WIDGET ) ? mContainer->asType<UIHTMLWidget>() : nullptr;
|
||||
if ( grid && grid->getLayoutWidthPolicy() == SizePolicy::MatchParent ) {
|
||||
Float matchWidth = grid->getMatchParentWidth();
|
||||
if ( matchWidth > 0.f && eeabs( matchWidth - grid->getPixelsSize().getWidth() ) > 0.01f )
|
||||
grid->setInternalPixelsWidth( matchWidth );
|
||||
}
|
||||
readContainerStyle();
|
||||
collectGridItems();
|
||||
resolveDefinitePlacements();
|
||||
@@ -959,13 +983,11 @@ void GridLayouter::updateLayout() {
|
||||
bool wPercentUnresolved = false;
|
||||
if ( grid && grid->getUIStyle() ) {
|
||||
const StyleSheetProperty* hprop = grid->getUIStyle()->getProperty( PropertyId::Height );
|
||||
hPercentUnresolved =
|
||||
hprop && StyleSheetLength::isPercentage( hprop->value() ) &&
|
||||
grid->getPixelsSize().getHeight() <= 0.f;
|
||||
hPercentUnresolved = hprop && StyleSheetLength::isPercentage( hprop->value() ) &&
|
||||
grid->getPixelsSize().getHeight() <= 0.f;
|
||||
const StyleSheetProperty* wprop = grid->getUIStyle()->getProperty( PropertyId::Width );
|
||||
wPercentUnresolved =
|
||||
wprop && StyleSheetLength::isPercentage( wprop->value() ) &&
|
||||
grid->getPixelsSize().getWidth() <= 0.f;
|
||||
wPercentUnresolved = wprop && StyleSheetLength::isPercentage( wprop->value() ) &&
|
||||
grid->getPixelsSize().getWidth() <= 0.f;
|
||||
}
|
||||
for ( int sizingPass = 0; sizingPass < 2 && needResize; ++sizingPass ) {
|
||||
needResize = false;
|
||||
@@ -980,9 +1002,8 @@ void GridLayouter::updateLayout() {
|
||||
contentH += row.baseSize;
|
||||
if ( mRows.size() > 1 )
|
||||
contentH += static_cast<Float>( mRows.size() - 1 ) * mRowGap;
|
||||
contentH +=
|
||||
mContainer->getPixelsContentOffset().Top +
|
||||
mContainer->getPixelsContentOffset().Bottom;
|
||||
contentH += mContainer->getPixelsContentOffset().Top +
|
||||
mContainer->getPixelsContentOffset().Bottom;
|
||||
if ( contentH > 0.f ) {
|
||||
mContainer->setInternalPixelsHeight( contentH );
|
||||
needResize = true;
|
||||
@@ -990,8 +1011,7 @@ void GridLayouter::updateLayout() {
|
||||
}
|
||||
|
||||
if ( wPercentUnresolved ) {
|
||||
const StyleSheetProperty* wprop =
|
||||
grid->getUIStyle()->getProperty( PropertyId::Width );
|
||||
const StyleSheetProperty* wprop = grid->getUIStyle()->getProperty( PropertyId::Width );
|
||||
std::string v = wprop->value();
|
||||
v.pop_back();
|
||||
Float pct = 0.f;
|
||||
@@ -1347,7 +1367,7 @@ void GridLayouter::autoPlaceItems() {
|
||||
|
||||
int cursorCol = 1;
|
||||
int cursorRow = 1;
|
||||
bool isRowFlow = true;
|
||||
bool isRowFlow = mAutoFlow == CSSGridAutoFlow::Row;
|
||||
|
||||
auto occupyRange = [&]( int r, int c, int w, int h ) {
|
||||
for ( int dr = 0; dr < h; ++dr )
|
||||
|
||||
@@ -696,6 +696,17 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
if ( !checkPropertyDefinition( attribute ) )
|
||||
return false;
|
||||
|
||||
auto applyGridLineShorthand = []( const std::string& value, auto setStart, auto setEnd ) {
|
||||
size_t slash = value.find( '/' );
|
||||
if ( slash == std::string::npos ) {
|
||||
setStart( String::trim( value ) );
|
||||
setEnd( "auto" );
|
||||
} else {
|
||||
setStart( String::trim( value.substr( 0, slash ) ) );
|
||||
setEnd( String::trim( value.substr( slash + 1 ) ) );
|
||||
}
|
||||
};
|
||||
|
||||
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Display: {
|
||||
setDisplay( CSSDisplayHelper::fromString( attribute.asString() ) );
|
||||
@@ -845,6 +856,20 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
setGridColumnEnd( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridRow: {
|
||||
applyGridLineShorthand(
|
||||
attribute.asString(),
|
||||
[this]( const std::string& value ) { setGridRowStart( value ); },
|
||||
[this]( const std::string& value ) { setGridRowEnd( value ); } );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridColumn: {
|
||||
applyGridLineShorthand(
|
||||
attribute.asString(),
|
||||
[this]( const std::string& value ) { setGridColumnStart( value ); },
|
||||
[this]( const std::string& value ) { setGridColumnEnd( value ); } );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridArea: {
|
||||
setGridArea( attribute.asString() );
|
||||
return true;
|
||||
|
||||
@@ -233,6 +233,44 @@ UTEST( GridAutoPlacement, rowFlowFillsColumnsThenRows ) {
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( GridAutoPlacement, columnFlowFillsRowsThenColumns ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Grid Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false,
|
||||
true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
init_grid_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIHTMLWidget* grid = UIHTMLWidget::New();
|
||||
grid->setParent( sceneNode->getRoot() );
|
||||
grid->setDisplay( CSSDisplay::Grid );
|
||||
grid->setGridTemplateRows( "50px 50px" );
|
||||
grid->setGridAutoFlow( CSSGridAutoFlow::Column );
|
||||
grid->setPixelsSize( 500, 200 );
|
||||
grid->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* i1 = UIHTMLWidget::New();
|
||||
i1->setParent( grid );
|
||||
UIHTMLWidget* i2 = UIHTMLWidget::New();
|
||||
i2->setParent( grid );
|
||||
UIHTMLWidget* i3 = UIHTMLWidget::New();
|
||||
i3->setParent( grid );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
auto* layouter = static_cast<GridLayouter*>( grid->getLayouter() );
|
||||
const auto& items = layouter->getItems();
|
||||
|
||||
ASSERT_EQ( items.size(), 3u );
|
||||
EXPECT_EQ( items[0].resolvedRowStart, 1 );
|
||||
EXPECT_EQ( items[0].resolvedColumnStart, 1 );
|
||||
EXPECT_EQ( items[1].resolvedRowStart, 2 );
|
||||
EXPECT_EQ( items[1].resolvedColumnStart, 1 );
|
||||
EXPECT_EQ( items[2].resolvedRowStart, 1 );
|
||||
EXPECT_EQ( items[2].resolvedColumnStart, 2 );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( GridAutoPlacement, definiteLeavesHoleForSparse ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Grid Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false,
|
||||
@@ -1717,6 +1755,79 @@ UTEST( GridContainer, downloadGridLayout ) {
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( GridContainer, gridTestFixturePlacesSpanningItems ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 650, "UIHTML Grid Fixture Test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings() );
|
||||
init_grid_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
|
||||
|
||||
std::string htmlContent;
|
||||
ASSERT_TRUE( FileSystem::fileGet( "assets/html/grid_test.html", htmlContent ) );
|
||||
sceneNode->loadLayoutFromString( EE::UI::Tools::HTMLFormatter::HTMLtoXML( htmlContent ) );
|
||||
sceneNode->update( Seconds( 1 ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* wrapperNode = sceneNode->getRoot()->findByClass( "wrapper" );
|
||||
ASSERT_TRUE( nullptr != wrapperNode );
|
||||
ASSERT_TRUE( wrapperNode->isWidget() );
|
||||
|
||||
auto* one = sceneNode->getRoot()->findByClass( "one" );
|
||||
auto* two = sceneNode->getRoot()->findByClass( "two" );
|
||||
auto* three = sceneNode->getRoot()->findByClass( "three" );
|
||||
auto* four = sceneNode->getRoot()->findByClass( "four" );
|
||||
auto* five = sceneNode->getRoot()->findByClass( "five" );
|
||||
auto* six = sceneNode->getRoot()->findByClass( "six" );
|
||||
ASSERT_TRUE( nullptr != one );
|
||||
ASSERT_TRUE( nullptr != two );
|
||||
ASSERT_TRUE( nullptr != three );
|
||||
ASSERT_TRUE( nullptr != four );
|
||||
ASSERT_TRUE( nullptr != five );
|
||||
ASSERT_TRUE( nullptr != six );
|
||||
ASSERT_TRUE( one->isWidget() );
|
||||
ASSERT_TRUE( two->isWidget() );
|
||||
ASSERT_TRUE( three->isWidget() );
|
||||
ASSERT_TRUE( four->isWidget() );
|
||||
ASSERT_TRUE( five->isWidget() );
|
||||
ASSERT_TRUE( six->isWidget() );
|
||||
|
||||
auto* oneWidget = one->asType<UIWidget>();
|
||||
auto* twoWidget = two->asType<UIWidget>();
|
||||
auto* threeWidget = three->asType<UIWidget>();
|
||||
auto* fourWidget = four->asType<UIWidget>();
|
||||
auto* fiveWidget = five->asType<UIWidget>();
|
||||
auto* sixWidget = six->asType<UIWidget>();
|
||||
|
||||
Float wrapperW = wrapperNode->asType<UIWidget>()->getPixelsSize().getWidth();
|
||||
EXPECT_GT( wrapperW, 900.f );
|
||||
EXPECT_LT( wrapperW, 950.f );
|
||||
|
||||
EXPECT_GT( oneWidget->getPixelsSize().getWidth(), 600.f );
|
||||
EXPECT_GT( twoWidget->getPixelsSize().getWidth(), 600.f );
|
||||
EXPECT_GT( threeWidget->getPixelsSize().getWidth(), 290.f );
|
||||
EXPECT_GT( fourWidget->getPixelsSize().getWidth(), 290.f );
|
||||
EXPECT_GT( fiveWidget->getPixelsSize().getWidth(), 290.f );
|
||||
EXPECT_GT( sixWidget->getPixelsSize().getWidth(), 290.f );
|
||||
|
||||
EXPECT_GT( oneWidget->getPixelsSize().getHeight(), 95.f );
|
||||
EXPECT_GT( twoWidget->getPixelsSize().getHeight(), 200.f );
|
||||
EXPECT_GT( threeWidget->getPixelsSize().getHeight(), 300.f );
|
||||
EXPECT_GT( fourWidget->getPixelsSize().getHeight(), 95.f );
|
||||
EXPECT_GT( fiveWidget->getPixelsSize().getHeight(), 95.f );
|
||||
EXPECT_GT( sixWidget->getPixelsSize().getHeight(), 95.f );
|
||||
|
||||
EXPECT_NEAR( oneWidget->getPixelsPosition().y, twoWidget->getPixelsPosition().y, 1.f );
|
||||
EXPECT_GT( twoWidget->getPixelsPosition().x, oneWidget->getPixelsPosition().x + 300.f );
|
||||
EXPECT_GT( threeWidget->getPixelsPosition().y, oneWidget->getPixelsPosition().y + 95.f );
|
||||
EXPECT_GT( fourWidget->getPixelsPosition().y, twoWidget->getPixelsPosition().y + 200.f );
|
||||
EXPECT_NEAR( fiveWidget->getPixelsPosition().y, sixWidget->getPixelsPosition().y, 1.f );
|
||||
EXPECT_GT( sixWidget->getPixelsPosition().x, fiveWidget->getPixelsPosition().x + 300.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( GridContainer, newsblurReducedGrid ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 650, "UIHTML Grid NewsBlur Test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
|
||||
Reference in New Issue
Block a user