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
File diff suppressed because it is too large Load Diff
+26 -1
View File
@@ -241,7 +241,10 @@ Requirements:
- **Gaps:** `row-gap`/`column-gap` with `gap` shorthand. `gap: normal` resolves to 0px in flexbox. - **Gaps:** `row-gap`/`column-gap` with `gap` shorthand. `gap: normal` resolves to 0px in flexbox.
- **`min-width: auto` / `min-height: auto`:** Content-based minimum prevents flex items from collapsing below their min-content size. The `overflow` property suppresses this minimum when set to non-visible. - **`min-width: auto` / `min-height: auto`:** Content-based minimum prevents flex items from collapsing below their min-content size. The `overflow` property suppresses this minimum when set to non-visible.
- **`visibility: collapse`:** Collapsed items are invisible and zero-sized on the main axis, but their cross size still contributes to the flex line cross size (preserving layout stability). - **`visibility: collapse`:** Collapsed items are invisible and zero-sized on the main axis, but their cross size still contributes to the flex line cross size (preserving layout stability).
- **`order`-modified paint order:** `UIHTMLWidget::drawChildren()` override stable-sorts children by CSS `order` when flex items have differing values. For `row-reverse`/`column-reverse`, the sorted list is also reversed before painting. - **`order`-modified paint order:** `UIHTMLWidget::drawChildren()` stable-sorts children by CSS
`order` when flex items have differing values. Per CSS Flexbox §4.3, painting uses
order-modified document order; `row-reverse`/`column-reverse` changes layout direction but does
not reverse paint order.
- **Container baseline:** After layout, the flex container's baseline is available via `FlexLayouter::getBaseline()` for use by outer formatting contexts. - **Container baseline:** After layout, the flex container's baseline is available via `FlexLayouter::getBaseline()` for use by outer formatting contexts.
### Out-Of-Flow Positioning ### Out-Of-Flow Positioning
@@ -257,6 +260,28 @@ Relative positioning should preserve normal-flow space and then offset painting/
Floats are represented in `RichText::CustomBlock` with `CSSFloat`/`CSSClear` metadata and handled by the float-aware RichText path. Float placement is edge-aligned and should not be altered by inline baseline alignment. Floats are represented in `RichText::CustomBlock` with `CSSFloat`/`CSSClear` metadata and handled by the float-aware RichText path. Float placement is edge-aligned and should not be altered by inline baseline alignment.
### Paint Order And Supported Stacking Contexts
`UIHTMLWidget` owns HTML box paint ordering and matching reverse-order hit testing. Its lazy paint
cache implements the supported CSS 2 Appendix E phases: negative positioned stacking groups,
in-flow content, non-positioned floats, positioned descendants with `z-index: auto` or `0`, and
positive positioned stacking groups. Equal-category items retain CSS tree order; flex and grid
containers use `order`-modified document order as their input tree order.
Positioned descendants and child stacking groups can participate in the nearest ancestor stacking
scope even when they are nested below ordinary blocks or floats. Such promoted nodes remain in their
DOM hierarchy for layout and ownership. Painting replays the skipped ancestors' transforms and exact
border/content/padding clip type, and hit testing applies the same ancestor clips. A real stacking
group is atomic: its descendant z-indices cannot escape and interleave with sibling groups.
The current stacking-context trigger subset consists of positioned boxes with an applicable
non-`auto` `z-index`, fixed and sticky positioned boxes (including `z-index: auto`), and flex/grid
items with non-`auto` `z-index`. Relative and absolute boxes with `z-index: auto` participate in the
positioned-auto phase without becoming atomic. Opacity, transforms, filters, isolation, containment,
and other modern stacking-context triggers are not yet modeled as CSS stacking groups. Inline
fragment background/text sub-phases remain owned by `RichText`; implementing their complete Appendix
E interleaving requires fragment-level paint records rather than widget-level sorting.
## Pixel Math ## Pixel Math
All layouters must use pixel (`Px`) APIs for layout calculations: All layouters must use pixel (`Px`) APIs for layout calculations:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

@@ -2,11 +2,11 @@
#define EE_UI_UIABSTRACTTABLEVIEW_HPP #define EE_UI_UIABSTRACTTABLEVIEW_HPP
#include <eepp/math/rect.hpp> #include <eepp/math/rect.hpp>
#include <eepp/thirdparty/nlohmann/json_fwd.hpp>
#include <eepp/ui/abstract/uiabstractview.hpp> #include <eepp/ui/abstract/uiabstractview.hpp>
#include <eepp/ui/uitablecell.hpp> #include <eepp/ui/uitablecell.hpp>
#include <eepp/ui/uitableheadercolumn.hpp> #include <eepp/ui/uitableheadercolumn.hpp>
#include <eepp/ui/uitablerow.hpp> #include <eepp/ui/uitablerow.hpp>
#include <eepp/thirdparty/nlohmann/json_fwd.hpp>
#include <unordered_map> #include <unordered_map>
using namespace EE::Math; using namespace EE::Math;
@@ -212,7 +212,10 @@ class EE_API UIAbstractTableView : public UIAbstractView {
bool manuallySet{ false }; bool manuallySet{ false };
UIPushButton* widget{ nullptr }; UIPushButton* widget{ nullptr };
void setWidth( Float w, bool manuallySet = false ); void setWidth( Float w, bool manuallySet = false ) {
width = w;
this->manuallySet = manuallySet;
}
}; };
Float mRowHeight{ 0 }; Float mRowHeight{ 0 };
+72 -5
View File
@@ -12,6 +12,7 @@ class RichText;
namespace EE { namespace UI { namespace EE { namespace UI {
class UILayouter; class UILayouter;
class UIHTMLWidget;
enum class CSSFormattingRole : Uint8 { enum class CSSFormattingRole : Uint8 {
Inline, Inline,
@@ -30,6 +31,16 @@ struct CSSUsedMargins {
Uint8 autoSides{ 0 }; Uint8 autoSides{ 0 };
}; };
struct CSSZIndex {
int value{ 0 };
bool isAuto{ true };
bool operator==( const CSSZIndex& other ) const {
return value == other.value && isAuto == other.isAuto;
}
bool operator!=( const CSSZIndex& other ) const { return !( *this == other ); }
};
struct UIHTMLWidgetFlexState { struct UIHTMLWidgetFlexState {
CSSFlexDirection direction{ CSSFlexDirection::Row }; CSSFlexDirection direction{ CSSFlexDirection::Row };
CSSFlexWrap wrap{ CSSFlexWrap::NoWrap }; CSSFlexWrap wrap{ CSSFlexWrap::NoWrap };
@@ -62,6 +73,22 @@ struct UIHTMLWidgetGridState {
CSSJustifySelf justifySelf{ CSSJustifySelf::Auto }; CSSJustifySelf justifySelf{ CSSJustifySelf::Auto };
}; };
enum class HTMLPaintCategory : Uint8 {
NegativePositioned,
NormalFlow,
Float,
PositionedAutoOrZero,
PositivePositioned,
};
struct UIHTMLPaintOrderCache {
SmallVector<Node*, 16> items;
Uint32 rebuildCount{ 0 };
bool dirty{ true };
};
using UIHTMLPaintAncestorVector = SmallVector<UIHTMLWidget*, 8>;
class EE_API UIHTMLWidget : public UILayout { class EE_API UIHTMLWidget : public UILayout {
public: public:
static UIHTMLWidget* New(); static UIHTMLWidget* New();
@@ -131,10 +158,22 @@ class EE_API UIHTMLWidget : public UILayout {
void setOffsets( const Rectf& offsets ); void setOffsets( const Rectf& offsets );
int getZIndex() const { return mZIndex; } const CSSZIndex& getCSSZIndex() const { return mZIndex; }
bool hasAutoZIndex() const { return mZIndex.isAuto; }
int getZIndex() const { return mZIndex.value; }
void setZIndex( int zIndex ); void setZIndex( int zIndex );
void setZIndexAuto();
bool isCSSPositioned() const { return mPosition != CSSPosition::Static; }
bool hasApplicableZIndex() const;
bool createsSupportedStackingGroup() const;
bool getNeedsOrderSort() const { return mNeedsOrderSort; } bool getNeedsOrderSort() const { return mNeedsOrderSort; }
void setNeedsOrderSort( bool val ); void setNeedsOrderSort( bool val );
@@ -308,10 +347,16 @@ class EE_API UIHTMLWidget : public UILayout {
virtual void onChildCountChange( Node* child, const bool& removed ); virtual void onChildCountChange( Node* child, const bool& removed );
void updateZIndexSortFlag();
void buildDrawOrderVector( SmallVector<Node*, 127>& out ) const; void buildDrawOrderVector( SmallVector<Node*, 127>& out ) const;
Uint32 getPaintOrderRebuildCount() const;
std::vector<Node*> debugGetHTMLPaintOrder() const;
std::vector<Node*> debugGetHTMLHitTestOrder() const;
bool isHTMLPaintPromoted() const { return mHTMLPaintOwner != nullptr; }
Float getBaseline() const; Float getBaseline() const;
Float getContainingBlockContentWidth() const; Float getContainingBlockContentWidth() const;
@@ -389,14 +434,20 @@ class EE_API UIHTMLWidget : public UILayout {
std::string mBottomEq{ "auto" }; std::string mBottomEq{ "auto" };
std::string mLeftEq{ "auto" }; std::string mLeftEq{ "auto" };
Rectf mOffsets{ 0, 0, 0, 0 }; Rectf mOffsets{ 0, 0, 0, 0 };
int mZIndex{ 0 }; CSSZIndex mZIndex;
bool mOverflowCreatesBlockFormattingContext{ false }; bool mOverflowCreatesBlockFormattingContext{ false };
bool mNeedsOrderSort{ false }; bool mNeedsOrderSort{ false };
bool mNeedsZIndexSort{ false }; bool mNeedsPaintOrder{ false };
bool mHasStackingDescendant{ false };
mutable bool mHasPromotedChild{ false };
mutable bool mHasActivePromotions{ false };
Uint8 mPercentageMargins{ 0 }; Uint8 mPercentageMargins{ 0 };
UILayouter* mLayouter{ nullptr }; UILayouter* mLayouter{ nullptr };
UIHTMLWidgetFlexState* mFlexState{ nullptr }; UIHTMLWidgetFlexState* mFlexState{ nullptr };
UIHTMLWidgetGridState* mGridState{ nullptr }; UIHTMLWidgetGridState* mGridState{ nullptr };
mutable UIHTMLPaintOrderCache* mPaintOrderCache{ nullptr };
mutable const UIHTMLWidget* mHTMLPaintOwner{ nullptr };
mutable UIHTMLPaintAncestorVector* mHTMLPaintAncestors{ nullptr };
UnorderedMap<std::string, StyleSheetProperty> mDataProperties; UnorderedMap<std::string, StyleSheetProperty> mDataProperties;
Uint32 mScrollCb{ 0 }; Uint32 mScrollCb{ 0 };
@@ -406,6 +457,22 @@ class EE_API UIHTMLWidget : public UILayout {
void updateScrollListeners(); void updateScrollListeners();
void onScrollTargetPositionChange(); void onScrollTargetPositionChange();
void invalidatePaintOrder();
void updatePaintOrderFlag();
const SmallVector<Node*, 16>& getPaintOrder() const;
bool isHTMLStackingScope() const;
void buildCSSChildOrder( SmallVector<Node*, 16>& out ) const;
void resetPromotedPaintState() const;
void collectStackingScopeItems( UIHTMLWidget* container, SmallVector<Node*, 16>& out,
bool directChildren ) const;
void collectStackingScopeChild( Node* child, SmallVector<Node*, 16>& out,
bool directChildren ) const;
void promoteHTMLPaintNode( UIHTMLWidget* widget ) const;
void drawHTMLPaintNode( Node* node );
bool containsHTMLPaintClipPoint( const Vector2f& point ) const;
bool canHitHTMLPaintNode( Node* node, const Vector2f& point ) const;
bool needsHTMLPaintTraversal() const;
bool shouldSkipHTMLPaintNode( Node* node ) const;
}; };
}} // namespace EE::UI }} // namespace EE::UI
@@ -1483,9 +1483,4 @@ UITableCell* UIAbstractTableView::getCellFromIndex( const ModelIndex& index ) co
return nullptr; return nullptr;
} }
void UIAbstractTableView::ColumnData::setWidth( Float w, bool manSet ) {
width = w;
manuallySet = manSet;
}
}}} // namespace EE::UI::Abstract }}} // namespace EE::UI::Abstract
+337 -62
View File
@@ -3,6 +3,7 @@
#include <eepp/ui/css/stylesheetlength.hpp> #include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/flexlayouter.hpp> #include <eepp/ui/flexlayouter.hpp>
#include <eepp/ui/gridlayouter.hpp> #include <eepp/ui/gridlayouter.hpp>
#include <eepp/ui/uiborderdrawable.hpp>
#include <eepp/ui/uihtmlwidget.hpp> #include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uilayouter.hpp> #include <eepp/ui/uilayouter.hpp>
#include <eepp/ui/uilayoutermanager.hpp> #include <eepp/ui/uilayoutermanager.hpp>
@@ -162,6 +163,8 @@ UIHTMLWidget::~UIHTMLWidget() {
eeSAFE_DELETE( mLayouter ); eeSAFE_DELETE( mLayouter );
eeSAFE_DELETE( mFlexState ); eeSAFE_DELETE( mFlexState );
eeSAFE_DELETE( mGridState ); eeSAFE_DELETE( mGridState );
eeSAFE_DELETE( mPaintOrderCache );
eeSAFE_DELETE( mHTMLPaintAncestors );
} }
UILayouter* UIHTMLWidget::getLayouter() { UILayouter* UIHTMLWidget::getLayouter() {
@@ -223,6 +226,7 @@ void UIHTMLWidget::setDisplay( CSSDisplay display ) {
} }
onDisplayChange(); onDisplayChange();
updatePaintOrderFlag();
} }
} }
@@ -351,6 +355,8 @@ void UIHTMLWidget::setVisibility( CSSVisibility val ) {
void UIHTMLWidget::setCSSPosition( CSSPosition position ) { void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
if ( mPosition != position ) { if ( mPosition != position ) {
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 ) { if ( position == CSSPosition::Absolute || position == CSSPosition::Fixed ) {
// Out-of-flow elements should not stretch to their containing block // Out-of-flow elements should not stretch to their containing block
// until updateOutOfFlowPosition() computes the correct size from CSS // until updateOutOfFlowPosition() computes the correct size from CSS
@@ -369,6 +375,8 @@ void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
void UIHTMLWidget::setCSSFloat( CSSFloat cssFloat ) { void UIHTMLWidget::setCSSFloat( CSSFloat cssFloat ) {
if ( mFloat != cssFloat ) { if ( mFloat != cssFloat ) {
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 // 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 // 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. // 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 ) { void UIHTMLWidget::setZIndex( int zIndex ) {
mZIndex = zIndex; const CSSZIndex value{ zIndex, false };
if ( mZIndex == value )
return;
mZIndex = value;
Node* p = getParent(); Node* p = getParent();
if ( p && p->isType( UI_TYPE_HTML_WIDGET ) ) 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 ) { void UIHTMLWidget::setNeedsOrderSort( bool val ) {
mNeedsOrderSort = val; if ( mNeedsOrderSort != val ) {
mNeedsOrderSort = val;
invalidatePaintOrder();
}
} }
void UIHTMLWidget::updateZIndexSortFlag() { void UIHTMLWidget::invalidatePaintOrder() {
bool needs = false; for ( Node* node = this; node; node = node->getParent() ) {
for ( Node* child = getFirstChild(); child; child = child->getNextNode() ) { if ( node->isType( UI_TYPE_HTML_WIDGET ) ) {
if ( child->isType( UI_TYPE_HTML_WIDGET ) && auto* widget = node->asType<UIHTMLWidget>();
child->asType<UIHTMLWidget>()->getZIndex() != 0 ) { if ( widget->mPaintOrderCache )
needs = true; widget->mPaintOrderCache->dirty = true;
break;
} }
} }
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 ) { void UIHTMLWidget::onChildCountChange( Node* child, const bool& removed ) {
UILayout::onChildCountChange( child, removed ); UILayout::onChildCountChange( child, removed );
if ( !removed ) updatePaintOrderFlag();
updateZIndexSortFlag();
else if ( child->isType( UI_TYPE_HTML_WIDGET ) &&
child->asType<UIHTMLWidget>()->getZIndex() != 0 )
updateZIndexSortFlag();
} }
void UIHTMLWidget::buildDrawOrderVector( SmallVector<Node*, 127>& out ) const { void UIHTMLWidget::buildDrawOrderVector( SmallVector<Node*, 127>& out ) const {
bool flexSort = false; const auto& paintOrder = getPaintOrder();
bool directionReverse = false; out.insert( out.end(), paintOrder.begin(), paintOrder.end() );
}
if ( isFlex() ) { static HTMLPaintCategory getPaintCategory( Node* node ) {
CSSFlexDirection dir = getFlexDirection(); // This is the supported CSS 2 Appendix E subset. Inline fragments remain owned by RichText, so
directionReverse = // their background/text sub-phases cannot be split here without fragment-level paint records.
dir == CSSFlexDirection::RowReverse || dir == CSSFlexDirection::ColumnReverse; if ( !node->isType( UI_TYPE_HTML_WIDGET ) )
flexSort = mNeedsOrderSort || directionReverse; 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() ) for ( Node* child = getFirstChild(); child; child = child->getNextNode() )
out.push_back( child ); out.push_back( child );
if ( !isFlex() && !isGrid() )
if ( flexSort && mNeedsOrderSort ) { return;
if ( mNeedsOrderSort ) {
std::stable_sort( out.begin(), out.end(), []( Node* a, Node* b ) { std::stable_sort( out.begin(), out.end(), []( Node* a, Node* b ) {
int aOrder = ( a->isWidget() && a->isType( UI_TYPE_HTML_WIDGET ) ) const int aOrder =
? a->asType<UIHTMLWidget>()->getOrder() a->isType( UI_TYPE_HTML_WIDGET ) ? a->asType<UIHTMLWidget>()->getOrder() : 0;
: 0; const int bOrder =
int bOrder = ( b->isWidget() && b->isType( UI_TYPE_HTML_WIDGET ) ) b->isType( UI_TYPE_HTML_WIDGET ) ? b->asType<UIHTMLWidget>()->getOrder() : 0;
? b->asType<UIHTMLWidget>()->getOrder()
: 0;
return aOrder < bOrder; 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() { void UIHTMLWidget::resetPromotedPaintState() const {
bool needsSort = mNeedsOrderSort || mNeedsZIndexSort; 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() ) { void UIHTMLWidget::promoteHTMLPaintNode( UIHTMLWidget* widget ) const {
CSSFlexDirection dir = getFlexDirection(); mHasActivePromotions = true;
if ( dir == CSSFlexDirection::RowReverse || dir == CSSFlexDirection::ColumnReverse ) widget->mHTMLPaintOwner = this;
needsSort = true; 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(); UILayout::drawChildren();
return; return;
} }
SmallVector<Node*, 127> sortedChildren; for ( auto* child : getPaintOrder() ) {
buildDrawOrderVector( sortedChildren ); if ( shouldSkipHTMLPaintNode( child ) )
continue;
for ( auto* child : sortedChildren ) {
if ( child->isVisible() ) if ( child->isVisible() )
child->nodeDraw(); drawHTMLPaintNode( child );
} }
} }
Node* UIHTMLWidget::overFind( const Vector2f& point ) { Node* UIHTMLWidget::overFind( const Vector2f& point ) {
if ( !mNeedsOrderSort && !mNeedsZIndexSort ) if ( !needsHTMLPaintTraversal() )
return UILayout::overFind( point ); return UILayout::overFind( point );
Node* pOver = nullptr; Node* pOver = nullptr;
@@ -602,11 +861,14 @@ Node* UIHTMLWidget::overFind( const Vector2f& point ) {
writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 );
mSceneNode->addMouseOverNode( this ); mSceneNode->addMouseOverNode( this );
SmallVector<Node*, 127> sortedChildren; const auto& sortedChildren = getPaintOrder();
buildDrawOrderVector( sortedChildren );
// 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 ) { for ( auto it = sortedChildren.rbegin(); it != sortedChildren.rend(); ++it ) {
if ( shouldSkipHTMLPaintNode( *it ) )
continue;
if ( !canHitHTMLPaintNode( *it, point ) )
continue;
Node* childOver = ( *it )->overFind( point ); Node* childOver = ( *it )->overFind( point );
if ( childOver ) { if ( childOver ) {
pOver = childOver; pOver = childOver;
@@ -698,6 +960,8 @@ void UIHTMLWidget::setOrder( int val ) {
auto* fs = ensureFlexState(); auto* fs = ensureFlexState();
if ( fs->order != val ) { if ( fs->order != val ) {
fs->order = val; fs->order = val;
if ( Node* parent = getParent(); parent && parent->isType( UI_TYPE_HTML_WIDGET ) )
parent->asType<UIHTMLWidget>()->invalidatePaintOrder();
notifyLayoutAttrChange( LayoutInvalidation::ContainerLayout ); notifyLayoutAttrChange( LayoutInvalidation::ContainerLayout );
} }
} }
@@ -899,7 +1163,7 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::Left: case PropertyId::Left:
return mLeftEq; return mLeftEq;
case PropertyId::ZIndex: case PropertyId::ZIndex:
return String::toString( mZIndex ); return mZIndex.isAuto ? "auto" : String::toString( mZIndex.value );
case PropertyId::AlignmentBaseline: case PropertyId::AlignmentBaseline:
return std::string( CSSBaselineAlignmentHelper::toString( mBaselineAlign ) ); return std::string( CSSBaselineAlignmentHelper::toString( mBaselineAlign ) );
case PropertyId::FlexDirection: case PropertyId::FlexDirection:
@@ -1043,7 +1307,10 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
return applied; return applied;
} }
case PropertyId::ZIndex: { case PropertyId::ZIndex: {
setZIndex( attribute.asInt() ); if ( String::trim( attribute.asString() ) == "auto" )
setZIndexAuto();
else
setZIndex( attribute.asInt() );
return true; return true;
} }
case PropertyId::Top: { case PropertyId::Top: {
@@ -1523,6 +1790,14 @@ void UIHTMLWidget::updateScrollListeners() {
void UIHTMLWidget::onParentChange() { void UIHTMLWidget::onParentChange() {
UILayout::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(); updateScrollListeners();
} }
+51 -5
View File
@@ -2258,7 +2258,7 @@ UTEST( FlexContainer, orderPaintSortNegatives ) {
Engine::destroySingleton(); Engine::destroySingleton();
} }
UTEST( FlexContainer, directionReversePaintColumnReverse ) { UTEST( FlexContainer, directionReverseLayoutKeepsColumnPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default, Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, WindowBackend::Default, 32, {}, 1, false,
true ), true ),
@@ -2294,13 +2294,20 @@ UTEST( FlexContainer, directionReversePaintColumnReverse ) {
EXPECT_GT( child1->getPixelsPosition().y, child2->getPixelsPosition().y ); EXPECT_GT( child1->getPixelsPosition().y, child2->getPixelsPosition().y );
EXPECT_GT( child2->getPixelsPosition().y, child3->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() ); EXPECT_FALSE( flex->getNeedsOrderSort() );
Engine::destroySingleton(); Engine::destroySingleton();
} }
UTEST( FlexContainer, directionReversePaintRowReverse ) { UTEST( FlexContainer, directionReverseLayoutKeepsRowPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default, Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, WindowBackend::Default, 32, {}, 1, false,
true ), true ),
@@ -2336,13 +2343,20 @@ UTEST( FlexContainer, directionReversePaintRowReverse ) {
EXPECT_GT( child1->getPixelsPosition().x, child2->getPixelsPosition().x ); EXPECT_GT( child1->getPixelsPosition().x, child2->getPixelsPosition().x );
EXPECT_GT( child2->getPixelsPosition().x, child3->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() ); EXPECT_FALSE( flex->getNeedsOrderSort() );
Engine::destroySingleton(); Engine::destroySingleton();
} }
UTEST( FlexContainer, directionReversePaintWithOrderSort ) { UTEST( FlexContainer, directionReverseLayoutUsesOrderModifiedPaintOrder ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default, Engine::instance()->createWindow( WindowSettings( 1024, 650, "Flex Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, WindowBackend::Default, 32, {}, 1, false,
true ), true ),
@@ -2388,6 +2402,38 @@ UTEST( FlexContainer, directionReversePaintWithOrderSort ) {
EXPECT_LT( child3->getPixelsPosition().y, child2->getPixelsPosition().y ); EXPECT_LT( child3->getPixelsPosition().y, child2->getPixelsPosition().y );
EXPECT_TRUE( flex->getNeedsOrderSort() ); 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(); Engine::destroySingleton();
} }
+265
View File
@@ -127,6 +127,271 @@ UTEST( UIHTMLFloat, property_GetPropertyString ) {
eeDelete( w ); 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 ) { UTEST( UIHTMLFloat, richtext_NoFloatLayout_NoChange ) {
init_float_test(); init_float_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();