Don't let <head> participate in layouting.

This commit is contained in:
Martín Lucas Golini
2026-05-19 18:49:38 -03:00
parent ea1a148b30
commit d27590753a
5 changed files with 86 additions and 6 deletions

View File

@@ -128,6 +128,7 @@ enum UINodeType {
UI_TYPE_DIFF_VIEW,
UI_TYPE_BR,
UI_TYPE_HTML_HTML,
UI_TYPE_HTML_HEAD,
UI_TYPE_HTML_BODY,
UI_TYPE_HTML_LIST_ITEM,
UI_TYPE_HTML_IMAGE,

View File

@@ -11,7 +11,13 @@ class EE_API UIRichText : public UIHTMLWidget {
public:
enum class IntrinsicMode { None, Min, Max };
enum class WhiteSpaceCollapse { Collapse, Preserve, PreserveBreaks, PreserveSpaces, BreakSpaces };
enum class WhiteSpaceCollapse {
Collapse,
Preserve,
PreserveBreaks,
PreserveSpaces,
BreakSpaces
};
static WhiteSpaceCollapse toWhiteSpaceCollapse( std::string val );
@@ -213,6 +219,16 @@ class EE_API UIHTMLBody : public UIRichText {
UIHTMLBody( const std::string& tag = "body" );
};
class EE_API UIHTMLHead : public UIWidget {
public:
static UIHTMLHead* New();
virtual Uint32 getType() const override;
bool isType( const Uint32& type ) const override;
protected:
UIHTMLHead();
};
class EE_API UILineBreak : public UIRichText {
public:
static UILineBreak* New( const std::string& tag );

View File

@@ -211,6 +211,23 @@ void UIHTMLBody::updateLayout() {
}
}
UIHTMLHead* UIHTMLHead::New() {
return eeNew( UIHTMLHead, () );
}
UIHTMLHead::UIHTMLHead() : UIWidget() {
mVisible = false;
mEnabled = false;
}
Uint32 UIHTMLHead::getType() const {
return UI_TYPE_HTML_HEAD;
}
bool UIHTMLHead::isType( const Uint32& type ) const {
return UIHTMLHead::getType() == type ? true : UIWidget::isType( type );
}
UIRichText* UIRichText::NewHtml() {
auto* html = UIHTMLHtml::New( "html" );
html->setClipType( ClipType::None );
@@ -1028,6 +1045,10 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
UIWidget* widget = node->asType<UIWidget>();
// Skip <head> - it must not participate in layout
if ( widget->isType( UI_TYPE_HTML_HEAD ) )
return;
bool handled = false;
if ( widget->isType( UI_TYPE_HTML_WIDGET ) && widget->asType<UIHTMLWidget>()->isInline() ) {

View File

@@ -236,7 +236,7 @@ void UIWidgetCreator::createBaseWidgetList() {
};
registeredWidget["aside"] = [] { return UIRichText::NewWithTag( "aside" ); };
registeredWidget["html"] = UIRichText::NewHtml;
registeredWidget["head"] = [] { return UIWidget::NewWithTag( "head" ); };
registeredWidget["head"] = UIHTMLHead::New;
registeredWidget["body"] = UIRichText::NewBody;
registeredWidget["form"] = [] { return UIHTMLForm::New(); };
registeredWidget["table"] = UIHTMLTable::New;

View File

@@ -30,6 +30,7 @@ using namespace EE::Window;
using namespace EE::Scene;
using namespace EE::UI;
using namespace EE::UI::CSS;
using namespace EE::UI::Tools;
// Helper: create a basic scene for RichText tests
static UI::UISceneNode* createRichTextScene() {
@@ -879,8 +880,7 @@ UTEST( UITextNode_BlockLayouter, OverFindHitsAnchorWhenMatchingText ) {
if ( !anchor->getHitBoxes().empty() ) {
const Rectf& firstHb = anchor->getHitBoxes()[0];
Vector2f hitPos = anchor->convertToWorldSpace(
{ firstHb.Left + 1, firstHb.Top + 1 } );
Vector2f hitPos = anchor->convertToWorldSpace( { firstHb.Left + 1, firstHb.Top + 1 } );
Node* hitNode = rt->overFind( hitPos );
EXPECT_EQ( hitNode, anchor );
}
@@ -914,8 +914,7 @@ UTEST( UITextNode_BlockLayouter, NestedSpanOverFindHitsInnerSpan ) {
if ( !inner->getHitBoxes().empty() ) {
const Rectf& firstHb = inner->getHitBoxes()[0];
Vector2f hitPos = inner->convertToWorldSpace(
{ firstHb.Left + 1, firstHb.Top + 1 } );
Vector2f hitPos = inner->convertToWorldSpace( { firstHb.Left + 1, firstHb.Top + 1 } );
Node* hitNode = rt->overFind( hitPos );
EXPECT_TRUE( hitNode == inner || hitNode->inParentTreeOf( inner ) );
}
@@ -994,6 +993,49 @@ UTEST( UITextNode_EdgeCases, DirectChildOfRichText ) {
// Suite: UITextNode_RegressionTests
// ============================================================
UTEST( UITextNode_Regression, BackgroundPositioningBodyYWithLineHeight ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );
String xml = R"xml(
<!doctype html>
<html lang="en">
<head>
<style>
html {
color: #555;
line-height: 1.5;
font-size: 16px;
}
body {
padding: 0 10px 0 10px;
margin: 0;
border-top: 4px solid #b45f38;
}
</style>
<body id="body">
<header class="site-header">
<nav class="site-nav">
<a class="menu-link" href="/">Home</a>
</nav>
</header>
</body>
</html>
)xml";
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( xml ) );
sceneNode->update( Time::Zero );
auto* body = sceneNode->find<UIWidget>( "body" );
ASSERT_TRUE( body != nullptr );
// Body must be at Y=0 - the <head> element should NOT participate in layout
// and should NOT create an empty line with line-height applied.
EXPECT_NEAR( body->getPixelsPosition().y, 0.f, 1.f );
destroyRichTextScene( sceneNode );
}
UTEST( UITextNode_Regression, WhitespaceCollapseDoesNotCreateSpuriousNodes ) {
auto sceneNode = createRichTextScene();
ASSERT_TRUE( sceneNode != nullptr );