Fixes in HTML td height.

This commit is contained in:
Martín Lucas Golini
2026-06-15 02:36:16 -03:00
parent d04f4502db
commit 3fa38a2574
3 changed files with 175 additions and 18 deletions
+8 -1
View File
@@ -1,6 +1,7 @@
#include <eepp/core/containers.hpp>
#include <eepp/graphics/richtext.hpp>
#include <eepp/ui/blocklayouter.hpp>
#include <eepp/ui/uihtmltable.hpp>
#include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uirichtext.hpp>
#include <eepp/ui/uistyle.hpp>
@@ -27,6 +28,11 @@ static bool isStretchedFlexItem( UIHTMLWidget* widget ) {
return false;
}
static bool isTableCellInTableRow( UIWidget* widget ) {
return widget && widget->isType( UI_TYPE_HTML_TABLE_CELL ) && widget->getParent() &&
widget->getParent()->isType( UI_TYPE_HTML_TABLE_ROW );
}
Float BlockLayouter::getMinIntrinsicWidth() {
computeIntrinsicWidths();
return mMinIntrinsicWidth;
@@ -106,7 +112,8 @@ void BlockLayouter::updateLayout() {
{ mContainer->lengthFromValue( *prop ), mContainer->getPixelsSize().getHeight() } );
}
if ( mContainer->getLayoutHeightPolicy() == SizePolicy::Fixed && mContainer->getUIStyle() &&
if ( !isTableCellInTableRow( mContainer ) &&
mContainer->getLayoutHeightPolicy() == SizePolicy::Fixed && mContainer->getUIStyle() &&
( prop = mContainer->getUIStyle()->getProperty( PropertyId::Height ) ) ) {
mContainer->setInternalPixelsSize(
{ mContainer->getPixelsSize().getWidth(), mContainer->lengthFromValue( *prop ) } );
+50 -17
View File
@@ -8,6 +8,36 @@ static inline Float sanitizeFloat( Float val ) {
return std::isfinite( val ) ? val : 0.f;
}
static Float specifiedHeightPx( UIWidget* widget ) {
if ( widget == nullptr || widget->getUIStyle() == nullptr )
return 0.f;
const StyleSheetProperty* prop = widget->getUIStyle()->getProperty( PropertyId::Height );
if ( prop == nullptr )
return 0.f;
return sanitizeFloat( widget->lengthFromValue( *prop ) );
}
static Float normalFlowChildrenBottomPx( UIWidget* widget ) {
if ( widget == nullptr )
return 0.f;
Float bottom = 0.f;
for ( Node* child = widget->getFirstChild(); child; child = child->getNextNode() ) {
if ( !child->isWidget() || !child->isVisible() )
continue;
if ( child->isType( UI_TYPE_HTML_WIDGET ) && child->asType<UIHTMLWidget>()->isOutOfFlow() )
continue;
UIWidget* childWidget = child->asType<UIWidget>();
const Float childTop = childWidget->getPixelsPosition().y;
bottom = std::max( bottom, childTop + childWidget->getPixelsSize().getHeight() );
bottom = std::max( bottom, childTop + normalFlowChildrenBottomPx( childWidget ) );
}
return sanitizeFloat( bottom );
}
void TableLayouter::setTableLayout( TableLayout layout ) {
if ( layout != mTableLayout ) {
mTableLayout = layout;
@@ -479,9 +509,9 @@ void TableLayouter::updateLayout() {
Uint32 colIndex = 0;
for ( Uint32 c = 0; c < columnCount; ++c ) {
UIHTMLTableCell* cell = mCells[start + c];
cell->beginAttributesTransaction();
cell->setLayoutWidthPolicy( SizePolicy::Fixed );
cell->setLayoutHeightPolicy( SizePolicy::WrapContent );
Float cellSpecifiedHeight = specifiedHeightPx( cell );
cell->mWidthPolicy = SizePolicy::Fixed;
cell->mHeightPolicy = SizePolicy::WrapContent;
Uint32 cellColspan = cell->getColSpan();
Float cellWidth = 0;
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
@@ -490,19 +520,30 @@ void TableLayouter::updateLayout() {
if ( cellColspan > 1 )
cellWidth += ( cellColspan - 1 ) * mCellspacing;
cell->setPixelsSize( cellWidth, cell->getPixelsSize().getHeight() );
cell->updateLayout();
cell->setLayoutHeightPolicy( SizePolicy::Fixed );
cell->endAttributesTransaction();
rowHeight = std::max( rowHeight, cell->getPixelsSize().getHeight() );
cell->UIRichText::updateLayout();
cell->mHeightPolicy = SizePolicy::Fixed;
// CSS Tables: a single-row table-cell height contributes to the row minimum
// height. It must not clamp content that needs more space.
Float cellMinHeight =
std::max( cell->getPixelsSize().getHeight(), cellSpecifiedHeight );
if ( cellSpecifiedHeight > 0.f )
cellMinHeight = std::max( cellMinHeight, normalFlowChildrenBottomPx( cell ) );
rowHeight = std::max( rowHeight, cellMinHeight );
colIndex += cellColspan;
}
UIHTMLTableRow* row = mRows[r];
// CSS Tables: row height is also a minimum, not a hard height.
if ( row->getLayoutHeightPolicy() == SizePolicy::Fixed )
rowHeight = std::max( rowHeight, specifiedHeightPx( row ) );
Float currentX = mCellspacing;
colIndex = 0;
for ( Uint32 c = 0; c < columnCount; ++c ) {
UIHTMLTableCell* cell = mCells[start + c];
cell->beginAttributesTransaction();
cell->setPixelsPosition( currentX, 0 );
cell->mWidthPolicy = SizePolicy::Fixed;
cell->mHeightPolicy = SizePolicy::Fixed;
Uint32 cellColspan = cell->getColSpan();
Float cellWidth = 0;
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
@@ -510,19 +551,11 @@ void TableLayouter::updateLayout() {
}
if ( cellColspan > 1 )
cellWidth += ( cellColspan - 1 ) * mCellspacing;
cell->setPixelsSize( cellWidth, rowHeight );
cell->endAttributesTransaction();
cell->setInternalPixelsSize( { cellWidth, rowHeight } );
currentX += cellWidth + mCellspacing;
colIndex += cellColspan;
}
UIHTMLTableRow* row = mRows[r];
// Respect the row's specified height (e.g., <tr style="height: 10px">)
if ( row->getLayoutHeightPolicy() == SizePolicy::Fixed ) {
Float rowSpecifiedHeight = row->getPropertyHeight();
if ( rowSpecifiedHeight > 0.f )
rowHeight = std::max( rowHeight, rowSpecifiedHeight );
}
row->setPixelsSize( containerWidth - paddingH, rowHeight );
if ( r == 0 && mCells[start]->getParent() &&
+117
View File
@@ -24,6 +24,7 @@
#include <eepp/ui/uitextspan.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/window/engine.hpp>
#include <eepp/window/input.hpp>
#include <limits>
using namespace EE;
@@ -1869,6 +1870,122 @@ UTEST( UIHTMLTable, basicLayout ) {
destroyRichTextScene( sceneNode );
}
UTEST( UIHTMLTable, cellHeightIsMinimumForInlineContentHitTest ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );
String xml = R"xml(
<table id="table" layout_width="200dp" layout_height="wrap_content">
<tr>
<td id="cell" style="height: 10px; line-height: 24px">
<a id="link" href="#">Link</a>
</td>
</tr>
</table>
)xml";
sceneNode->loadLayoutFromString( xml );
sceneNode->update( Time::Zero );
auto* table = sceneNode->find<UIHTMLTable>( "table" );
auto* cell = sceneNode->find<UIHTMLTableCell>( "cell" );
auto* link = sceneNode->find<UIAnchorSpan>( "link" );
ASSERT_TRUE( table != nullptr );
ASSERT_TRUE( cell != nullptr );
ASSERT_TRUE( link != nullptr );
ASSERT_FALSE( link->getHitBoxes().empty() );
const Rectf& hitBox = link->getHitBoxes().front();
EXPECT_GT( cell->getPixelsSize().getHeight(), 10.f );
EXPECT_GE( cell->getPixelsSize().getHeight(), link->getPixelsPosition().y + hitBox.Bottom );
Vector2f hitPos = link->convertToWorldSpace(
{ hitBox.Left + 1.f, eemax( hitBox.Top, hitBox.Bottom - 1.f ) } );
Node* hitNode = table->overFind( hitPos );
EXPECT_EQ( hitNode, link );
destroyRichTextScene( sceneNode );
}
UTEST( UIHTMLTable, cellHeightStretchesToRowMinimum ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );
String xml = R"xml(
<table id="table" layout_width="200dp" layout_height="wrap_content">
<tr>
<td id="logo"><img id="img" style="width: 18px; height: 18px; display: block" /></td>
<td id="nav" style="height: 10px">Nav</td>
</tr>
</table>
)xml";
sceneNode->loadLayoutFromString( xml );
sceneNode->update( Time::Zero );
auto* logo = sceneNode->find<UIHTMLTableCell>( "logo" );
auto* nav = sceneNode->find<UIHTMLTableCell>( "nav" );
ASSERT_TRUE( logo != nullptr );
ASSERT_TRUE( nav != nullptr );
EXPECT_GT( logo->getPixelsSize().getHeight(), 10.f );
EXPECT_EQ( nav->getPixelsSize().getHeight(), logo->getPixelsSize().getHeight() );
destroyRichTextScene( sceneNode );
}
UTEST( UIHTMLTable, tableCellAnchorHoverRelayoutsRichText ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );
String xml = R"xml(
<table id="table" layout_width="200dp" layout_height="wrap_content">
<style>
a { text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
<tr>
<td id="logo"><img id="img" style="width: 18px; height: 18px; display: block" /></td>
<td id="nav" style="height: 10px"><a id="link" href="#">Nav</a></td>
</tr>
</table>
)xml";
sceneNode->loadLayoutFromString( xml );
sceneNode->update( Time::Zero );
auto* nav = sceneNode->find<UIHTMLTableCell>( "nav" );
auto* link = sceneNode->find<UIAnchorSpan>( "link" );
ASSERT_TRUE( nav != nullptr );
ASSERT_TRUE( link != nullptr );
ASSERT_FALSE( link->getHitBoxes().empty() );
auto renderedAnchorStyle = [&nav]() -> Uint32 {
for ( const auto& line : nav->getRichText().getLines() ) {
for ( const auto& span : line.spans ) {
if ( span.type == RichText::RenderSpan::Type::Text && span.text &&
span.text->getString() == "Nav" )
return span.text->getStyle();
}
}
return 0;
};
EXPECT_FALSE( ( renderedAnchorStyle() & Text::Underlined ) != 0 );
EXPECT_GT( nav->getPixelsSize().getHeight(), 10.f );
const Rectf& hitBox = link->getHitBoxes().front();
Vector2f hoverPos = link->convertToWorldSpace( { hitBox.Left + 1.f, hitBox.Top + 1.f } );
EXPECT_EQ( sceneNode->overFind( hoverPos ), link );
sceneNode->getEventDispatcher()->getInput()->setMousePos( hoverPos.asInt() );
sceneNode->update( Time::Zero );
EXPECT_TRUE( ( renderedAnchorStyle() & Text::Underlined ) != 0 );
EXPECT_GT( nav->getPixelsSize().getHeight(), 10.f );
destroyRichTextScene( sceneNode );
}
UTEST( UIRichText, WhitespaceCollapseBRTest ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );