Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Martín Lucas Golini
2026-08-14 13:53:07 -03:00
9 changed files with 756 additions and 1618 deletions
@@ -1483,9 +1483,4 @@ UITableCell* UIAbstractTableView::getCellFromIndex( const ModelIndex& index ) co
return nullptr;
}
void UIAbstractTableView::ColumnData::setWidth( Float w, bool manSet ) {
width = w;
manuallySet = manSet;
}
}}} // namespace EE::UI::Abstract
+337 -62
View File
@@ -3,6 +3,7 @@
#include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/flexlayouter.hpp>
#include <eepp/ui/gridlayouter.hpp>
#include <eepp/ui/uiborderdrawable.hpp>
#include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uilayouter.hpp>
#include <eepp/ui/uilayoutermanager.hpp>
@@ -162,6 +163,8 @@ UIHTMLWidget::~UIHTMLWidget() {
eeSAFE_DELETE( mLayouter );
eeSAFE_DELETE( mFlexState );
eeSAFE_DELETE( mGridState );
eeSAFE_DELETE( mPaintOrderCache );
eeSAFE_DELETE( mHTMLPaintAncestors );
}
UILayouter* UIHTMLWidget::getLayouter() {
@@ -223,6 +226,7 @@ void UIHTMLWidget::setDisplay( CSSDisplay display ) {
}
onDisplayChange();
updatePaintOrderFlag();
}
}
@@ -351,6 +355,8 @@ void UIHTMLWidget::setVisibility( CSSVisibility val ) {
void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
if ( mPosition != position ) {
mPosition = position;
if ( Node* parent = getParent(); parent && parent->isType( UI_TYPE_HTML_WIDGET ) )
parent->asType<UIHTMLWidget>()->updatePaintOrderFlag();
if ( position == CSSPosition::Absolute || position == CSSPosition::Fixed ) {
// Out-of-flow elements should not stretch to their containing block
// until updateOutOfFlowPosition() computes the correct size from CSS
@@ -369,6 +375,8 @@ void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
void UIHTMLWidget::setCSSFloat( CSSFloat cssFloat ) {
if ( mFloat != cssFloat ) {
mFloat = cssFloat;
if ( Node* parent = getParent(); parent && parent->isType( UI_TYPE_HTML_WIDGET ) )
parent->asType<UIHTMLWidget>()->updatePaintOrderFlag();
// A width:auto block normally fills its containing block, while a float uses the CSS
// shrink-to-fit width. Represent that used-width distinction with WrapContent so the
// floated box's own layouter cannot stretch it back after its parent measured it.
@@ -494,103 +502,354 @@ void UIHTMLWidget::setOffsets( const Rectf& offsets ) {
}
void UIHTMLWidget::setZIndex( int zIndex ) {
mZIndex = zIndex;
const CSSZIndex value{ zIndex, false };
if ( mZIndex == value )
return;
mZIndex = value;
Node* p = getParent();
if ( p && p->isType( UI_TYPE_HTML_WIDGET ) )
p->asType<UIHTMLWidget>()->updateZIndexSortFlag();
p->asType<UIHTMLWidget>()->updatePaintOrderFlag();
}
void UIHTMLWidget::setZIndexAuto() {
if ( mZIndex.isAuto )
return;
mZIndex = {};
Node* p = getParent();
if ( p && p->isType( UI_TYPE_HTML_WIDGET ) )
p->asType<UIHTMLWidget>()->updatePaintOrderFlag();
}
bool UIHTMLWidget::hasApplicableZIndex() const {
if ( mZIndex.isAuto )
return false;
if ( isCSSPositioned() )
return true;
Node* parent = getParent();
return parent && parent->isType( UI_TYPE_HTML_WIDGET ) &&
( parent->asType<UIHTMLWidget>()->isFlex() || parent->asType<UIHTMLWidget>()->isGrid() );
}
bool UIHTMLWidget::createsSupportedStackingGroup() const {
return mPosition == CSSPosition::Fixed || mPosition == CSSPosition::Sticky ||
hasApplicableZIndex();
}
void UIHTMLWidget::setNeedsOrderSort( bool val ) {
mNeedsOrderSort = val;
if ( mNeedsOrderSort != val ) {
mNeedsOrderSort = val;
invalidatePaintOrder();
}
}
void UIHTMLWidget::updateZIndexSortFlag() {
bool needs = false;
for ( Node* child = getFirstChild(); child; child = child->getNextNode() ) {
if ( child->isType( UI_TYPE_HTML_WIDGET ) &&
child->asType<UIHTMLWidget>()->getZIndex() != 0 ) {
needs = true;
break;
void UIHTMLWidget::invalidatePaintOrder() {
for ( Node* node = this; node; node = node->getParent() ) {
if ( node->isType( UI_TYPE_HTML_WIDGET ) ) {
auto* widget = node->asType<UIHTMLWidget>();
if ( widget->mPaintOrderCache )
widget->mPaintOrderCache->dirty = true;
}
}
mNeedsZIndexSort = needs;
}
void UIHTMLWidget::updatePaintOrderFlag() {
bool needsPaintOrder = false;
bool hasStackingDescendant = false;
for ( Node* child = getFirstChild(); child; child = child->getNextNode() ) {
if ( !child->isType( UI_TYPE_HTML_WIDGET ) )
continue;
auto* htmlChild = child->asType<UIHTMLWidget>();
needsPaintOrder |=
( !isFlex() && !isGrid() && htmlChild->getCSSFloat() != CSSFloat::None ) ||
htmlChild->getCSSPosition() != CSSPosition::Static || htmlChild->hasApplicableZIndex();
hasStackingDescendant |= htmlChild->isCSSPositioned() ||
htmlChild->createsSupportedStackingGroup() ||
htmlChild->mHasStackingDescendant;
}
const bool stackingChanged = mHasStackingDescendant != hasStackingDescendant;
mNeedsPaintOrder = needsPaintOrder;
mHasStackingDescendant = hasStackingDescendant;
invalidatePaintOrder();
if ( stackingChanged ) {
Node* parent = getParent();
if ( parent && parent->isType( UI_TYPE_HTML_WIDGET ) )
parent->asType<UIHTMLWidget>()->updatePaintOrderFlag();
}
}
void UIHTMLWidget::onChildCountChange( Node* child, const bool& removed ) {
UILayout::onChildCountChange( child, removed );
if ( !removed )
updateZIndexSortFlag();
else if ( child->isType( UI_TYPE_HTML_WIDGET ) &&
child->asType<UIHTMLWidget>()->getZIndex() != 0 )
updateZIndexSortFlag();
updatePaintOrderFlag();
}
void UIHTMLWidget::buildDrawOrderVector( SmallVector<Node*, 127>& out ) const {
bool flexSort = false;
bool directionReverse = false;
const auto& paintOrder = getPaintOrder();
out.insert( out.end(), paintOrder.begin(), paintOrder.end() );
}
if ( isFlex() ) {
CSSFlexDirection dir = getFlexDirection();
directionReverse =
dir == CSSFlexDirection::RowReverse || dir == CSSFlexDirection::ColumnReverse;
flexSort = mNeedsOrderSort || directionReverse;
static HTMLPaintCategory getPaintCategory( Node* node ) {
// This is the supported CSS 2 Appendix E subset. Inline fragments remain owned by RichText, so
// their background/text sub-phases cannot be split here without fragment-level paint records.
if ( !node->isType( UI_TYPE_HTML_WIDGET ) )
return HTMLPaintCategory::NormalFlow;
const auto* widget = node->asType<UIHTMLWidget>();
const bool positioned = widget->isCSSPositioned();
const bool applicableZIndex = widget->hasApplicableZIndex();
Node* parent = widget->getParent();
const bool flexOrGridItem =
parent && parent->isType( UI_TYPE_HTML_WIDGET ) &&
( parent->asType<UIHTMLWidget>()->isFlex() || parent->asType<UIHTMLWidget>()->isGrid() );
if ( applicableZIndex && widget->getZIndex() < 0 )
return HTMLPaintCategory::NegativePositioned;
if ( !positioned && !applicableZIndex && !flexOrGridItem &&
widget->getCSSFloat() != CSSFloat::None )
return HTMLPaintCategory::Float;
if ( applicableZIndex && widget->getZIndex() > 0 )
return HTMLPaintCategory::PositivePositioned;
if ( positioned || applicableZIndex )
return HTMLPaintCategory::PositionedAutoOrZero;
return HTMLPaintCategory::NormalFlow;
}
bool UIHTMLWidget::isHTMLStackingScope() const {
if ( createsSupportedStackingGroup() )
return true;
for ( Node* parent = getParent(); parent; parent = parent->getParent() ) {
if ( parent->isType( UI_TYPE_HTML_WIDGET ) )
return false;
}
return true;
}
void UIHTMLWidget::buildCSSChildOrder( SmallVector<Node*, 16>& out ) const {
for ( Node* child = getFirstChild(); child; child = child->getNextNode() )
out.push_back( child );
if ( flexSort && mNeedsOrderSort ) {
if ( !isFlex() && !isGrid() )
return;
if ( mNeedsOrderSort ) {
std::stable_sort( out.begin(), out.end(), []( Node* a, Node* b ) {
int aOrder = ( a->isWidget() && a->isType( UI_TYPE_HTML_WIDGET ) )
? a->asType<UIHTMLWidget>()->getOrder()
: 0;
int bOrder = ( b->isWidget() && b->isType( UI_TYPE_HTML_WIDGET ) )
? b->asType<UIHTMLWidget>()->getOrder()
: 0;
const int aOrder =
a->isType( UI_TYPE_HTML_WIDGET ) ? a->asType<UIHTMLWidget>()->getOrder() : 0;
const int bOrder =
b->isType( UI_TYPE_HTML_WIDGET ) ? b->asType<UIHTMLWidget>()->getOrder() : 0;
return aOrder < bOrder;
} );
}
if ( flexSort && directionReverse )
std::reverse( out.begin(), out.end() );
if ( mNeedsZIndexSort ) {
std::stable_sort( out.begin(), out.end(), []( Node* a, Node* b ) {
int aZ =
( a->isType( UI_TYPE_HTML_WIDGET ) ) ? a->asType<UIHTMLWidget>()->getZIndex() : 0;
int bZ =
( b->isType( UI_TYPE_HTML_WIDGET ) ) ? b->asType<UIHTMLWidget>()->getZIndex() : 0;
return aZ < bZ;
} );
}
}
void UIHTMLWidget::drawChildren() {
bool needsSort = mNeedsOrderSort || mNeedsZIndexSort;
void UIHTMLWidget::resetPromotedPaintState() const {
for ( Node* child = getFirstChild(); child; child = child->getNextNode() ) {
if ( !child->isType( UI_TYPE_HTML_WIDGET ) )
continue;
auto* widget = child->asType<UIHTMLWidget>();
widget->mHTMLPaintOwner = nullptr;
if ( widget->mHTMLPaintAncestors )
widget->mHTMLPaintAncestors->clear();
widget->mHasPromotedChild = false;
if ( widget->mPaintOrderCache )
widget->mPaintOrderCache->dirty = true;
widget->resetPromotedPaintState();
}
}
if ( isFlex() ) {
CSSFlexDirection dir = getFlexDirection();
if ( dir == CSSFlexDirection::RowReverse || dir == CSSFlexDirection::ColumnReverse )
needsSort = true;
void UIHTMLWidget::promoteHTMLPaintNode( UIHTMLWidget* widget ) const {
mHasActivePromotions = true;
widget->mHTMLPaintOwner = this;
if ( !widget->mHTMLPaintAncestors )
widget->mHTMLPaintAncestors = eeNew( UIHTMLPaintAncestorVector, () );
for ( Node* parent = widget->getParent(); parent && parent != this;
parent = parent->getParent() ) {
if ( parent->isType( UI_TYPE_HTML_WIDGET ) )
widget->mHTMLPaintAncestors->push_back( parent->asType<UIHTMLWidget>() );
}
if ( widget->getParent()->isType( UI_TYPE_HTML_WIDGET ) )
widget->getParent()->asType<UIHTMLWidget>()->mHasPromotedChild = true;
}
void UIHTMLWidget::collectStackingScopeItems( UIHTMLWidget* container, SmallVector<Node*, 16>& out,
bool directChildren ) const {
if ( container->isFlex() || container->isGrid() ) {
SmallVector<Node*, 16> children;
container->buildCSSChildOrder( children );
for ( Node* child : children )
collectStackingScopeChild( child, out, directChildren );
return;
}
for ( Node* child = container->getFirstChild(); child; child = child->getNextNode() )
collectStackingScopeChild( child, out, directChildren );
}
void UIHTMLWidget::collectStackingScopeChild( Node* child, SmallVector<Node*, 16>& out,
bool directChildren ) const {
if ( directChildren )
out.push_back( child );
if ( !child->isType( UI_TYPE_HTML_WIDGET ) )
return;
auto* widget = child->asType<UIHTMLWidget>();
if ( widget->createsSupportedStackingGroup() ) {
if ( !directChildren ) {
out.push_back( widget );
promoteHTMLPaintNode( widget );
}
return;
}
if ( !directChildren && widget->isCSSPositioned() ) {
out.push_back( widget );
promoteHTMLPaintNode( widget );
}
collectStackingScopeItems( widget, out, false );
}
const SmallVector<Node*, 16>& UIHTMLWidget::getPaintOrder() const {
if ( !mPaintOrderCache )
mPaintOrderCache = eeNew( UIHTMLPaintOrderCache, () );
if ( !mPaintOrderCache->dirty )
return mPaintOrderCache->items;
auto& out = mPaintOrderCache->items;
out.clear();
const bool collectStacking = isHTMLStackingScope() && mHasStackingDescendant;
// A scope must clear its previous promotion metadata even when the last promoted descendant
// just stopped qualifying; otherwise its old parent can keep skipping the node indefinitely.
if ( mHasActivePromotions ) {
resetPromotedPaintState();
mHasActivePromotions = false;
}
if ( collectStacking ) {
collectStackingScopeItems( const_cast<UIHTMLWidget*>( this ), out, true );
} else {
buildCSSChildOrder( out );
}
if ( !needsSort ) {
if ( mNeedsPaintOrder || collectStacking ) {
std::stable_sort( out.begin(), out.end(), []( Node* a, Node* b ) {
const auto aCategory = getPaintCategory( a );
const auto bCategory = getPaintCategory( b );
if ( aCategory != bCategory )
return aCategory < bCategory;
if ( aCategory == HTMLPaintCategory::NegativePositioned ||
aCategory == HTMLPaintCategory::PositivePositioned )
return a->asType<UIHTMLWidget>()->getZIndex() <
b->asType<UIHTMLWidget>()->getZIndex();
return false;
} );
}
mPaintOrderCache->dirty = false;
++mPaintOrderCache->rebuildCount;
return out;
}
Uint32 UIHTMLWidget::getPaintOrderRebuildCount() const {
return mPaintOrderCache ? mPaintOrderCache->rebuildCount : 0;
}
std::vector<Node*> UIHTMLWidget::debugGetHTMLPaintOrder() const {
const auto& order = getPaintOrder();
return { order.begin(), order.end() };
}
std::vector<Node*> UIHTMLWidget::debugGetHTMLHitTestOrder() const {
const auto& order = getPaintOrder();
return { order.rbegin(), order.rend() };
}
void UIHTMLWidget::drawHTMLPaintNode( Node* node ) {
if ( !node->isType( UI_TYPE_HTML_WIDGET ) ||
node->asType<UIHTMLWidget>()->mHTMLPaintOwner != this ) {
node->nodeDraw();
return;
}
auto* promoted = node->asType<UIHTMLWidget>();
eeASSERT( promoted->mHTMLPaintAncestors != nullptr );
const auto& ancestors = *promoted->mHTMLPaintAncestors;
for ( auto it = ancestors.rbegin(); it != ancestors.rend(); ++it ) {
auto* ancestor = *it;
ancestor->matrixSet();
ancestor->smartClipStart( ancestor->getClipType(),
ancestor->isMeOrParentTreeScaledOrRotatedOrFrameBuffer() );
}
node->nodeDraw();
for ( auto* ancestor : ancestors ) {
ancestor->smartClipEnd( ancestor->getClipType(),
ancestor->isMeOrParentTreeScaledOrRotatedOrFrameBuffer() );
ancestor->matrixUnset();
}
}
bool UIHTMLWidget::containsHTMLPaintClipPoint( const Vector2f& point ) const {
if ( !isClipped() )
return true;
const Vector2f localPoint = convertToNodeSpace( point );
Rectf clipRect( Vector2f::Zero, getPixelsSize() );
switch ( getClipType() ) {
case ClipType::PaddingBox: {
const Rectf& padding = getPixelsPadding();
clipRect = Rectf( padding.Left, padding.Top,
getPixelsSize().getWidth() - padding.Left - padding.Right,
getPixelsSize().getHeight() - padding.Top - padding.Bottom );
break;
}
case ClipType::BorderBox: {
if ( mBorder ) {
const Rectf borderDiff = mBorder->getBorderBoxDiff();
clipRect = Rectf( borderDiff.Left, borderDiff.Top,
getPixelsSize().getWidth() + borderDiff.Right,
getPixelsSize().getHeight() + borderDiff.Bottom );
}
break;
}
case ClipType::ContentBox:
case ClipType::None:
break;
}
return clipRect.contains( localPoint );
}
bool UIHTMLWidget::canHitHTMLPaintNode( Node* node, const Vector2f& point ) const {
if ( !node->isType( UI_TYPE_HTML_WIDGET ) )
return true;
const auto* promoted = node->asType<UIHTMLWidget>();
if ( promoted->mHTMLPaintOwner != this || !promoted->mHTMLPaintAncestors )
return true;
for ( auto* ancestor : *promoted->mHTMLPaintAncestors ) {
if ( !ancestor->containsHTMLPaintClipPoint( point ) )
return false;
}
return true;
}
bool UIHTMLWidget::needsHTMLPaintTraversal() const {
if ( mNeedsOrderSort || mNeedsPaintOrder || mHasPromotedChild ||
( isHTMLStackingScope() && mHasStackingDescendant ) )
return true;
return false;
}
bool UIHTMLWidget::shouldSkipHTMLPaintNode( Node* node ) const {
return node->isType( UI_TYPE_HTML_WIDGET ) &&
node->asType<UIHTMLWidget>()->mHTMLPaintOwner != nullptr &&
node->asType<UIHTMLWidget>()->mHTMLPaintOwner != this;
}
void UIHTMLWidget::drawChildren() {
if ( !needsHTMLPaintTraversal() ) {
UILayout::drawChildren();
return;
}
SmallVector<Node*, 127> sortedChildren;
buildDrawOrderVector( sortedChildren );
for ( auto* child : sortedChildren ) {
for ( auto* child : getPaintOrder() ) {
if ( shouldSkipHTMLPaintNode( child ) )
continue;
if ( child->isVisible() )
child->nodeDraw();
drawHTMLPaintNode( child );
}
}
Node* UIHTMLWidget::overFind( const Vector2f& point ) {
if ( !mNeedsOrderSort && !mNeedsZIndexSort )
if ( !needsHTMLPaintTraversal() )
return UILayout::overFind( point );
Node* pOver = nullptr;
@@ -602,11 +861,14 @@ Node* UIHTMLWidget::overFind( const Vector2f& point ) {
writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 );
mSceneNode->addMouseOverNode( this );
SmallVector<Node*, 127> sortedChildren;
buildDrawOrderVector( sortedChildren );
const auto& sortedChildren = getPaintOrder();
// Iterate last-to-first: highest z-index = topmost = hit first
// Drawing and hit-testing share one sequence; reverse it so the last painted node wins.
for ( auto it = sortedChildren.rbegin(); it != sortedChildren.rend(); ++it ) {
if ( shouldSkipHTMLPaintNode( *it ) )
continue;
if ( !canHitHTMLPaintNode( *it, point ) )
continue;
Node* childOver = ( *it )->overFind( point );
if ( childOver ) {
pOver = childOver;
@@ -698,6 +960,8 @@ void UIHTMLWidget::setOrder( int val ) {
auto* fs = ensureFlexState();
if ( fs->order != val ) {
fs->order = val;
if ( Node* parent = getParent(); parent && parent->isType( UI_TYPE_HTML_WIDGET ) )
parent->asType<UIHTMLWidget>()->invalidatePaintOrder();
notifyLayoutAttrChange( LayoutInvalidation::ContainerLayout );
}
}
@@ -899,7 +1163,7 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::Left:
return mLeftEq;
case PropertyId::ZIndex:
return String::toString( mZIndex );
return mZIndex.isAuto ? "auto" : String::toString( mZIndex.value );
case PropertyId::AlignmentBaseline:
return std::string( CSSBaselineAlignmentHelper::toString( mBaselineAlign ) );
case PropertyId::FlexDirection:
@@ -1043,7 +1307,10 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
return applied;
}
case PropertyId::ZIndex: {
setZIndex( attribute.asInt() );
if ( String::trim( attribute.asString() ) == "auto" )
setZIndexAuto();
else
setZIndex( attribute.asInt() );
return true;
}
case PropertyId::Top: {
@@ -1523,6 +1790,14 @@ void UIHTMLWidget::updateScrollListeners() {
void UIHTMLWidget::onParentChange() {
UILayout::onParentChange();
// Promotion is derived from ancestry. Reparenting can detach this subtree before its former
// stacking scope gets a chance to rebuild, so clear all non-owning scope metadata here.
mHTMLPaintOwner = nullptr;
if ( mHTMLPaintAncestors )
mHTMLPaintAncestors->clear();
mHasPromotedChild = false;
resetPromotedPaintState();
invalidatePaintOrder();
updateScrollListeners();
}
+51 -5
View File
@@ -2258,7 +2258,7 @@ UTEST( FlexContainer, orderPaintSortNegatives ) {
Engine::destroySingleton();
}
UTEST( FlexContainer, directionReversePaintColumnReverse ) {
UTEST( FlexContainer, directionReverseLayoutKeepsColumnPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false,
true ),
@@ -2294,13 +2294,20 @@ UTEST( FlexContainer, directionReversePaintColumnReverse ) {
EXPECT_GT( child1->getPixelsPosition().y, child2->getPixelsPosition().y );
EXPECT_GT( child2->getPixelsPosition().y, child3->getPixelsPosition().y );
// All orders equal, so no order-based sort needed; direction-only reversal in drawChildren
SmallVector<Node*, 127> paintOrder;
flex->buildDrawOrderVector( paintOrder );
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], child1 );
EXPECT_EQ( paintOrder[1], child2 );
EXPECT_EQ( paintOrder[2], child3 );
// CSS Flexbox 4.3 uses order-modified document order, independent of flex-direction.
EXPECT_FALSE( flex->getNeedsOrderSort() );
Engine::destroySingleton();
}
UTEST( FlexContainer, directionReversePaintRowReverse ) {
UTEST( FlexContainer, directionReverseLayoutKeepsRowPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false,
true ),
@@ -2336,13 +2343,20 @@ UTEST( FlexContainer, directionReversePaintRowReverse ) {
EXPECT_GT( child1->getPixelsPosition().x, child2->getPixelsPosition().x );
EXPECT_GT( child2->getPixelsPosition().x, child3->getPixelsPosition().x );
// All orders equal so no order-based sort needed
SmallVector<Node*, 127> paintOrder;
flex->buildDrawOrderVector( paintOrder );
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], child1 );
EXPECT_EQ( paintOrder[1], child2 );
EXPECT_EQ( paintOrder[2], child3 );
// All orders equal, so the source order is also the order-modified document order.
EXPECT_FALSE( flex->getNeedsOrderSort() );
Engine::destroySingleton();
}
UTEST( FlexContainer, directionReversePaintWithOrderSort ) {
UTEST( FlexContainer, directionReverseLayoutUsesOrderModifiedPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false,
true ),
@@ -2388,6 +2402,38 @@ UTEST( FlexContainer, directionReversePaintWithOrderSort ) {
EXPECT_LT( child3->getPixelsPosition().y, child2->getPixelsPosition().y );
EXPECT_TRUE( flex->getNeedsOrderSort() );
SmallVector<Node*, 127> paintOrder;
flex->buildDrawOrderVector( paintOrder );
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], child2 );
EXPECT_EQ( paintOrder[1], child3 );
EXPECT_EQ( paintOrder[2], child1 );
Engine::destroySingleton();
}
UTEST( FlexContainer, floatDoesNotChangeFlexItemPaintPhase ) {
Engine::instance()->createWindow( WindowSettings( 640, 480, "Flex paint float",
WindowStyle::Default, WindowBackend::Default,
32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
init_flex_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto* flex = UIHTMLWidget::New();
flex->setParent( sceneNode->getRoot() );
flex->setDisplay( CSSDisplay::Flex );
auto* floated = UIHTMLWidget::New();
floated->setParent( flex );
floated->setCSSFloat( CSSFloat::Left );
auto* normal = UIHTMLWidget::New();
normal->setParent( flex );
sceneNode->updateDirtyLayouts();
SmallVector<Node*, 127> paintOrder;
flex->buildDrawOrderVector( paintOrder );
ASSERT_EQ( paintOrder.size(), 2u );
EXPECT_EQ( paintOrder[0], floated );
EXPECT_EQ( paintOrder[1], normal );
Engine::destroySingleton();
}
+265
View File
@@ -127,6 +127,271 @@ UTEST( UIHTMLFloat, property_GetPropertyString ) {
eeDelete( w );
}
UTEST( UIHTMLFloat, zIndexPreservesAutoAndApplicability ) {
auto* child = UIHTMLWidget::New();
EXPECT_TRUE( child->hasAutoZIndex() );
EXPECT_TRUE( child->getPropertyString( "z-index" ) == "auto" );
child->applyProperty( StyleSheetProperty( "z-index", "0" ) );
EXPECT_FALSE( child->hasAutoZIndex() );
EXPECT_EQ( child->getZIndex(), 0 );
EXPECT_FALSE( child->hasApplicableZIndex() );
child->setCSSPosition( CSSPosition::Relative );
EXPECT_TRUE( child->hasApplicableZIndex() );
EXPECT_TRUE( child->createsSupportedStackingGroup() );
child->applyProperty( StyleSheetProperty( "z-index", "-3" ) );
EXPECT_EQ( child->getZIndex(), -3 );
EXPECT_TRUE( child->getPropertyString( "z-index" ) == "-3" );
child->applyProperty( StyleSheetProperty( "z-index", "auto" ) );
EXPECT_TRUE( child->hasAutoZIndex() );
EXPECT_FALSE( child->createsSupportedStackingGroup() );
eeDelete( child );
}
UTEST( UIHTMLFloat, floatPaintsAndHitsAboveLaterNormalBlock ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto* parent = UIHTMLWidget::New();
parent->setParent( sceneNode->getRoot() );
parent->setPixelsSize( 300, 200 );
parent->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
auto* side = UIHTMLWidget::New();
side->setParent( parent );
side->setPixelsSize( 100, 100 );
side->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
side->setCSSFloat( CSSFloat::Right );
auto* sideLink = UIHTMLWidget::New();
sideLink->setParent( side );
sideLink->setPixelsSize( 100, 100 );
sideLink->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
auto* content = UIHTMLWidget::New();
content->setParent( parent );
content->setPixelsSize( 300, 200 );
content->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
SmallVector<Node*, 127> paintOrder;
parent->buildDrawOrderVector( paintOrder );
ASSERT_EQ( paintOrder.size(), 2u );
EXPECT_EQ( paintOrder[0], content );
EXPECT_EQ( paintOrder[1], side );
EXPECT_EQ( parent->getPaintOrderRebuildCount(), 1u );
paintOrder.clear();
parent->buildDrawOrderVector( paintOrder );
EXPECT_EQ( parent->getPaintOrderRebuildCount(), 1u );
EXPECT_EQ( parent->overFind( { 10, 10 } ), sideLink );
EXPECT_EQ( parent->getPaintOrderRebuildCount(), 1u );
side->setCSSFloat( CSSFloat::None );
paintOrder.clear();
parent->buildDrawOrderVector( paintOrder );
EXPECT_EQ( paintOrder[0], side );
EXPECT_EQ( paintOrder[1], content );
EXPECT_EQ( parent->getPaintOrderRebuildCount(), 2u );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, positionedDescendantParticipatesInRootStackingScope ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto makeBox = []( Node* parent ) {
auto* box = UIHTMLWidget::New();
box->setParent( parent );
box->setPixelsSize( 200, 150 );
box->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
return box;
};
auto* root = makeBox( sceneNode->getRoot() );
auto* section = makeBox( root );
auto* popup = makeBox( section );
popup->setCSSPosition( CSSPosition::Absolute );
popup->setZIndex( 10 );
auto* overlay = makeBox( root );
overlay->setCSSPosition( CSSPosition::Relative );
overlay->setZIndex( 5 );
auto paintOrder = root->debugGetHTMLPaintOrder();
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], section );
EXPECT_EQ( paintOrder[1], overlay );
EXPECT_EQ( paintOrder[2], popup );
EXPECT_TRUE( popup->isHTMLPaintPromoted() );
EXPECT_EQ( root->overFind( { 10, 10 } ), popup );
section->setPixelsSize( 50, 50 );
section->setClipType( ClipType::ContentBox );
EXPECT_EQ( root->overFind( { 100, 100 } ), overlay );
section->setPixelsSize( 100, 100 );
section->setPadding( Rectf( 20, 20, 20, 20 ) );
section->setClipType( ClipType::PaddingBox );
EXPECT_EQ( root->overFind( { 10, 10 } ), overlay );
EXPECT_EQ( root->overFind( { 30, 30 } ), popup );
popup->setZIndexAuto();
paintOrder = root->debugGetHTMLPaintOrder();
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], section );
EXPECT_EQ( paintOrder[1], popup );
EXPECT_EQ( paintOrder[2], overlay );
EXPECT_TRUE( popup->isHTMLPaintPromoted() );
EXPECT_EQ( root->overFind( { 10, 10 } ), overlay );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, directStackingGroupIsAtomic ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto makeGroup = []( Node* parent, int zIndex ) {
auto* group = UIHTMLWidget::New();
group->setParent( parent );
group->setPixelsSize( 200, 150 );
group->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
group->setCSSPosition( CSSPosition::Relative );
group->setZIndex( zIndex );
return group;
};
auto* root = UIHTMLWidget::New();
root->setParent( sceneNode->getRoot() );
root->setPixelsSize( 200, 150 );
auto* lowerGroup = makeGroup( root, 1 );
auto* highInsideLower = makeGroup( lowerGroup, 1000 );
auto* upperGroup = makeGroup( root, 2 );
const auto paintOrder = root->debugGetHTMLPaintOrder();
ASSERT_EQ( paintOrder.size(), 2u );
EXPECT_EQ( paintOrder[0], lowerGroup );
EXPECT_EQ( paintOrder[1], upperGroup );
EXPECT_EQ( root->overFind( { 10, 10 } ), upperGroup );
EXPECT_FALSE( highInsideLower->isHTMLPaintPromoted() );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, positionedDescendantPromotesThroughFloat ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto makeBox = []( Node* parent ) {
auto* box = UIHTMLWidget::New();
box->setParent( parent );
box->setPixelsSize( 200, 150 );
box->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
return box;
};
auto* root = makeBox( sceneNode->getRoot() );
auto* floated = makeBox( root );
floated->setCSSFloat( CSSFloat::Left );
auto* popup = makeBox( floated );
popup->setCSSPosition( CSSPosition::Absolute );
popup->setZIndex( 10 );
auto* overlay = makeBox( root );
overlay->setCSSPosition( CSSPosition::Relative );
overlay->setZIndex( 5 );
const auto paintOrder = root->debugGetHTMLPaintOrder();
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], floated );
EXPECT_EQ( paintOrder[1], overlay );
EXPECT_EQ( paintOrder[2], popup );
EXPECT_EQ( root->overFind( { 10, 10 } ), popup );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, removingLastPromotionClearsSkipMetadata ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto* root = UIHTMLWidget::New();
root->setParent( sceneNode->getRoot() );
root->setPixelsSize( 200, 150 );
auto* section = UIHTMLWidget::New();
section->setParent( root );
section->setPixelsSize( 200, 150 );
auto* popup = UIHTMLWidget::New();
popup->setParent( section );
popup->setPixelsSize( 200, 150 );
popup->setCSSPosition( CSSPosition::Absolute );
popup->setZIndex( 10 );
root->debugGetHTMLPaintOrder();
EXPECT_TRUE( popup->isHTMLPaintPromoted() );
popup->setZIndexAuto();
popup->setCSSPosition( CSSPosition::Static );
root->debugGetHTMLPaintOrder();
EXPECT_FALSE( popup->isHTMLPaintPromoted() );
EXPECT_EQ( root->overFind( { 10, 10 } ), popup );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, nestedPositionedAutoParticipatesInAncestorPositionedPhase ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto* root = UIHTMLWidget::New();
root->setParent( sceneNode->getRoot() );
root->setPixelsSize( 200, 150 );
auto* section = UIHTMLWidget::New();
section->setParent( root );
section->setPixelsSize( 200, 150 );
auto* positionedAuto = UIHTMLWidget::New();
positionedAuto->setParent( section );
positionedAuto->setPixelsSize( 200, 150 );
positionedAuto->setCSSPosition( CSSPosition::Relative );
auto* floated = UIHTMLWidget::New();
floated->setParent( root );
floated->setPixelsSize( 200, 150 );
floated->setCSSFloat( CSSFloat::Left );
const auto paintOrder = root->debugGetHTMLPaintOrder();
ASSERT_EQ( paintOrder.size(), 3u );
EXPECT_EQ( paintOrder[0], section );
EXPECT_EQ( paintOrder[1], floated );
EXPECT_EQ( paintOrder[2], positionedAuto );
EXPECT_TRUE( positionedAuto->isHTMLPaintPromoted() );
EXPECT_EQ( root->overFind( { 10, 10 } ), positionedAuto );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, reparentingPromotedSubtreeClearsOldScopeMetadata ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
auto* oldRoot = UIHTMLWidget::New();
oldRoot->setParent( sceneNode->getRoot() );
oldRoot->setPixelsSize( 200, 150 );
auto* section = UIHTMLWidget::New();
section->setParent( oldRoot );
auto* popup = UIHTMLWidget::New();
popup->setParent( section );
popup->setPixelsSize( 100, 100 );
popup->setCSSPosition( CSSPosition::Absolute );
popup->setZIndex( 10 );
auto* newRoot = UIHTMLWidget::New();
newRoot->setParent( sceneNode->getRoot() );
newRoot->setPixelsSize( 200, 150 );
oldRoot->debugGetHTMLPaintOrder();
EXPECT_TRUE( popup->isHTMLPaintPromoted() );
popup->setParent( newRoot );
oldRoot->debugGetHTMLPaintOrder();
const auto newOrder = newRoot->debugGetHTMLPaintOrder();
ASSERT_EQ( newOrder.size(), 1u );
EXPECT_EQ( newOrder[0], popup );
EXPECT_FALSE( popup->isHTMLPaintPromoted() );
EXPECT_EQ( newRoot->overFind( { 10, 10 } ), popup );
Engine::destroySingleton();
}
UTEST( UIHTMLFloat, richtext_NoFloatLayout_NoChange ) {
init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();