Fixes for absolute positioning and some minor details.

This commit is contained in:
Martín Lucas Golini
2026-04-29 13:05:29 -03:00
parent 84331ad9ad
commit ae0fd6bc2b
11 changed files with 197 additions and 61 deletions

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
body {
background: #000000;
color: #FFFFFF;
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 8pt;
font-weight:normal;
text-align: left;
margin: 0;
padding: 0;
}
#main {
width: 400px;
position:absolute;
left:50%;
margin-left: -200px;
margin-top: 120px;
border: 0px;
}
.box {
width: 400px;
margin: 0 auto;
background-color: #333333;
text-align: center;
}
</style>
</head>
<body>
<div id="main">
<div class="box">
<div class="titlebox">File Upload</div>
<div class="login_inbox">
<div class="loginbox">
<div class="mini_titlebox">[LOGIN]</div>
<form method="post" action="?s=1">
<p>Username: <input name="Nombre" type="text" size="12" /></p>
<p>Password: <input name="Password" type="password" size="12" /></p>
<p><input type="submit" name="Submit" value="Entrar" /></p>
</form>
</div>
</div>
</div>
<div class="box">
<div class="titlebox">Download Files</div>
<div class="inbox">
<a href="upload/">ENTER</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -18,8 +18,6 @@ class EE_API UIHTMLTable : public UIHTMLWidget {
virtual bool isType( const Uint32& type ) const;
virtual void updateLayout();
virtual Float getMinIntrinsicWidth() const;
virtual Float getMaxIntrinsicWidth() const;

View File

@@ -61,6 +61,10 @@ class EE_API UIHTMLWidget : public UILayout {
protected:
CSSDisplay mDisplay{ CSSDisplay::Block };
CSSPosition mPosition{ CSSPosition::Static };
std::string mTopEq{ "auto" };
std::string mRightEq{ "auto" };
std::string mBottomEq{ "auto" };
std::string mLeftEq{ "auto" };
Rectf mOffsets{ 0, 0, 0, 0 };
int mZIndex{ 0 };
UILayouter* mLayouter{ nullptr };

View File

@@ -24,6 +24,9 @@ class EE_API UILayout : public UIWidget {
bool isLayoutDirty() const { return mDirtyLayout; }
void onAutoSizeChild( UIWidget* child );
void setLayoutDirty();
protected:
friend class UISceneNode;
friend class UILayouter;
@@ -51,8 +54,6 @@ class EE_API UILayout : public UIWidget {
virtual void updateLayoutWrappingContents();
void setLayoutDirty();
bool setMatchParentIfNeededVerticalGrowth();
};

View File

@@ -129,8 +129,6 @@ class EE_API UIRichText : public UIHTMLWidget {
String getSelectionString() const;
virtual void updateLayout();
virtual RichText* getRichTextPtr() { return &mRichText; }
protected:

View File

@@ -431,10 +431,18 @@ void StyleSheetSpecification::registerDefaultProperties() {
registerProperty( "list-style-type", "none", true ).setType( PropertyType::String );
registerProperty( "list-style-position", "outside", true ).setType( PropertyType::String );
registerProperty( "list-style-image", "none" ).setType( PropertyType::String );
registerProperty( "top", "auto" ).setType( PropertyType::NumberLength );
registerProperty( "right", "auto" ).setType( PropertyType::NumberLength );
registerProperty( "bottom", "auto" ).setType( PropertyType::NumberLength );
registerProperty( "left", "auto" ).setType( PropertyType::NumberLength );
registerProperty( "top", "auto" )
.setType( PropertyType::NumberLength )
.setRelativeTarget( PropertyRelativeTarget::ContainingBlockHeight );
registerProperty( "right", "auto" )
.setType( PropertyType::NumberLength )
.setRelativeTarget( PropertyRelativeTarget::ContainingBlockWidth );
registerProperty( "bottom", "auto" )
.setType( PropertyType::NumberLength )
.setRelativeTarget( PropertyRelativeTarget::ContainingBlockHeight );
registerProperty( "left", "auto" )
.setType( PropertyType::NumberLength )
.setRelativeTarget( PropertyRelativeTarget::ContainingBlockWidth );
registerProperty( "z-index", "auto" ).setType( PropertyType::NumberInt );
registerProperty( "inner-widget-orientation", "widgeticontextbox" )

View File

@@ -85,16 +85,6 @@ Float UIHTMLTable::getMaxIntrinsicWidth() const {
return 0;
}
void UIHTMLTable::updateLayout() {
UILayouter* layouter = const_cast<UIHTMLTable*>( this )->getLayouter();
if ( layouter )
getLayouter()->updateLayout();
else
UIHTMLWidget::updateLayout();
mDirtyLayout = false;
}
Uint32 UIHTMLTable::onMessage( const NodeMessage* Msg ) {
switch ( Msg->getMsg() ) {
case NodeMessage::LayoutAttributeChange: {

View File

@@ -60,6 +60,10 @@ void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
void UIHTMLWidget::setOffsets( const Rectf& offsets ) {
if ( mOffsets != offsets ) {
mOffsets = offsets;
mTopEq = String::fromFloat( offsets.Top, "dp" );
mLeftEq = String::fromFloat( offsets.Left, "dp" );
mRightEq = String::fromFloat( offsets.Right, "dp" );
mBottomEq = String::fromFloat( offsets.Bottom, "dp" );
notifyLayoutAttrChange();
}
}
@@ -79,13 +83,13 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::Position:
return CSSPositionHelper::toString( mPosition );
case PropertyId::Top:
return String::fromFloat( mOffsets.Top, "dp" );
return mTopEq;
case PropertyId::Right:
return String::fromFloat( mOffsets.Right, "dp" );
return mRightEq;
case PropertyId::Bottom:
return String::fromFloat( mOffsets.Bottom, "dp" );
return mBottomEq;
case PropertyId::Left:
return String::fromFloat( mOffsets.Left, "dp" );
return mLeftEq;
case PropertyId::ZIndex:
return String::toString( mZIndex );
default:
@@ -111,34 +115,22 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
return true;
}
case PropertyId::Top: {
if ( attribute.asString() == "auto" )
mOffsets.Top = 0;
else
mOffsets.Top = lengthFromValueAsDp( attribute );
mTopEq = attribute.asString();
notifyLayoutAttrChange();
return true;
}
case PropertyId::Right: {
if ( attribute.asString() == "auto" )
mOffsets.Right = 0;
else
mOffsets.Right = lengthFromValueAsDp( attribute );
mRightEq = attribute.asString();
notifyLayoutAttrChange();
return true;
}
case PropertyId::Bottom: {
if ( attribute.asString() == "auto" )
mOffsets.Bottom = 0;
else
mOffsets.Bottom = lengthFromValueAsDp( attribute );
mBottomEq = attribute.asString();
notifyLayoutAttrChange();
return true;
}
case PropertyId::Left: {
if ( attribute.asString() == "auto" )
mOffsets.Left = 0;
else
mOffsets.Left = lengthFromValueAsDp( attribute );
mLeftEq = attribute.asString();
notifyLayoutAttrChange();
return true;
}
@@ -148,7 +140,6 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
return UILayout::applyProperty( attribute );
}
void UIHTMLWidget::updateLayout() {
if ( getLayouter() )
getLayouter()->updateLayout();
@@ -156,6 +147,8 @@ void UIHTMLWidget::updateLayout() {
UILayout::updateLayout();
positionOutOfFlowChildren();
mDirtyLayout = false;
}
UIWidget* UIHTMLWidget::getContainingBlock() {
@@ -195,11 +188,22 @@ void UIHTMLWidget::positionOutOfFlowChildren() {
if ( pos == CSSPosition::Absolute || pos == CSSPosition::Fixed ) {
UIWidget* cb = htmlChild->getContainingBlock();
if ( cb ) {
Rectf offsets = htmlChild->getOffsets();
Float top = PixelDensity::dpToPx( offsets.Top );
Float left = PixelDensity::dpToPx( offsets.Left );
Float top = htmlChild->mTopEq == "auto"
? 0
: htmlChild->lengthFromValue(
htmlChild->mTopEq,
CSS::PropertyRelativeTarget::ContainingBlockHeight, 0 );
Float left = htmlChild->mLeftEq == "auto"
? 0
: htmlChild->lengthFromValue(
htmlChild->mLeftEq,
CSS::PropertyRelativeTarget::ContainingBlockWidth, 0 );
Vector2f cbPos( cb->getPixelsContentOffset().Left, cb->getPixelsContentOffset().Top );
top += htmlChild->getLayoutPixelsMargin().Top;
left += htmlChild->getLayoutPixelsMargin().Left;
Vector2f cbPos( cb->getPixelsContentOffset().Left,
cb->getPixelsContentOffset().Top );
cbPos.x += left;
cbPos.y += top;

View File

@@ -10,7 +10,6 @@
#include <eepp/ui/uitextspan.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/ui/uiwidgetcreator.hpp>
#include <unordered_map>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>
@@ -94,11 +93,15 @@ bool UIHTMLBody::applyProperty( const StyleSheetProperty& attribute ) {
}
UIRichText* UIRichText::NewHtml() {
return UIHTMLHtml::New( "html" );
auto* html = UIHTMLHtml::New( "html" );
html->setClipType( ClipType::None );
return html;
}
UIRichText* UIRichText::NewBody() {
return UIHTMLBody::New( "body" );
auto* body = UIHTMLBody::New( "body" );
body->setClipType( ClipType::None );
return body;
}
UIRichText* UIRichText::NewBr() {
@@ -261,7 +264,7 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
break;
}
default:
return UILayout::applyProperty( attribute );
return UIHTMLWidget::applyProperty( attribute );
}
return true;
@@ -305,7 +308,7 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
? "center"
: ( getTextAlign() == TEXT_ALIGN_RIGHT ? "right" : "left" );
default:
return UILayout::getPropertyString( propertyDef, propertyIndex );
return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex );
}
}
@@ -701,16 +704,6 @@ void UIRichText::updateDefaultSpansStyle() {
}
}
void UIRichText::updateLayout() {
if ( getLayouter() ) {
getLayouter()->updateLayout();
} else {
UILayout::updateLayout();
}
mDirtyLayout = false;
}
Float UIRichText::getMinIntrinsicWidth() const {
if ( mWidthPolicy == SizePolicy::Fixed ) {
return getPropertyWidth();

View File

@@ -82,6 +82,7 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
auto urlBar = ui->find( "url_bar" )->asType<UITextInput>();
auto mainContainer = ui->find( "html_doc" );
mainContainer->asType<UIWidget>()->setClipType( ClipType::None );
auto backBtn = ui->find( "backbtn" )->asType<UIPushButton>();
auto fwdBtn = ui->find( "fwdbtn" )->asType<UIPushButton>();
auto scrollView = ui->find( "html_view" )->asType<UIScrollView>();
@@ -98,13 +99,31 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
if ( data.empty() )
return;
ui->ensureMainThread( [url, data, mainContainer, urlBar, ui, &app, scrollView, useHNDark] {
scrollView->removeEventsOfType( Event::OnSizeChange );
mainContainer->closeAllChildren();
scrollView->getVerticalScrollBar()->setValue( 0 );
ui->getStyleSheet().removeAllWithoutMarker( app.getStyleSheetDefaultMarker() );
ui->setURIFromURL( url );
auto urlStr = url.toString();
auto hash = String::hash( urlStr );
scrollView->getVerticalScrollBar()->setValue( 0 );
ui->loadLayoutFromString( HTMLFormatter::HTMLtoXML( data ), mainContainer, hash );
auto htmlNode = ui->findByType( UI_TYPE_HTML_HTML );
auto bodyNode = ui->findByType( UI_TYPE_HTML_BODY );
if ( htmlNode && bodyNode ) {
auto html = htmlNode->asType<UIHTMLHtml>();
auto body = bodyNode->asType<UIHTMLBody>();
html->setMinHeight( scrollView->getPixelsSize().getHeight() );
body->setMinHeight( scrollView->getPixelsSize().getHeight() );
scrollView->on( Event::OnSizeChange, [scrollView, html, body]( auto ) {
body->setMinHeight( scrollView->getSize().getHeight() );
body->setPixelsSize( { html->getPixelsSize().getWidth(), 0 } );
html->setMinHeight( scrollView->getSize().getHeight() );
html->setPixelsSize( { html->getPixelsSize().getWidth(), 0 } );
} );
body->on( Event::OnClose, [scrollView]( auto ) {
scrollView->removeEventsOfType( Event::OnSizeChange );
} );
}
urlBar->setText( urlStr );
if ( useHNDark && url.getAuthority() == "news.ycombinator.com" ) {

View File

@@ -196,3 +196,70 @@ UTEST( UIHTMLWidget, positionOutOfFlow_DoesNotAffectParentSize ) {
Engine::destroySingleton();
}
UTEST( UIHTMLWidget, positionOutOfFlow_PercentageAndMargin ) {
init_ui_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
UIHTMLWidget* rootContainer = UIHTMLWidget::New();
rootContainer->setParent( sceneNode->getRoot() );
rootContainer->setCSSPosition( CSSPosition::Relative );
rootContainer->setPixelsSize( 800, 600 );
rootContainer->setPixelsPosition( 0, 0 );
UIHTMLWidget* absoluteChild = UIHTMLWidget::New();
absoluteChild->setParent( rootContainer );
absoluteChild->setPixelsSize( 400, 400 );
// Emulate the CSS parsing via applyProperty
absoluteChild->applyProperty( StyleSheetProperty( "position", "absolute" ) );
absoluteChild->applyProperty( StyleSheetProperty( "left", "50%" ) );
absoluteChild->applyProperty( StyleSheetProperty( "margin-left", "-200px" ) );
absoluteChild->applyProperty( StyleSheetProperty( "margin-top", "120px" ) );
sceneNode->updateDirtyLayouts();
UIWidget* cb = absoluteChild->getContainingBlock();
EXPECT_EQ( cb, rootContainer );
Vector2f worldPos = absoluteChild->convertToWorldSpace( { 0, 0 } );
// left should be 50% of 800 (400) plus margin-left (-200) = 200
// top should be 0 + margin-top (120) = 120
EXPECT_NEAR( 200.f, worldPos.x, 1.f );
EXPECT_NEAR( 120.f, worldPos.y, 1.f );
Engine::destroySingleton();
}
#include <eepp/ui/tools/htmlformatter.hpp>
#include <eepp/ui/css/stylesheetparser.hpp>
UTEST( UIHTMLWidget, positionOutOfFlow_ComplexHTML ) {
init_ui_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
std::string html;
FileSystem::fileGet( "assets/html/absolute_position.html", html );
std::string xml = UI::Tools::HTMLFormatter::HTMLtoXML( html );
sceneNode->loadLayoutFromString( xml );
sceneNode->update( Milliseconds( 16 ) );
sceneNode->updateDirtyLayouts();
UIWidget* mainWidget = sceneNode->getRoot()->find<UIWidget>( "main" );
ASSERT_TRUE( mainWidget != nullptr );
EXPECT_GT( mainWidget->getPixelsSize().getHeight(), 0.f ); // This is not standard in HTML!
Vector2f worldPos = mainWidget->convertToWorldSpace( { 0, 0 } );
// Window size is 1024x650
// left: 50% of 1024 = 512
// margin-left: -200px
// 512 - 200 = 312
EXPECT_NEAR( 312.f, worldPos.x, 1.f );
// top should just be margin-top
EXPECT_NEAR( 120.f, worldPos.y, 1.f );
Engine::destroySingleton();
}