Communicate the reason for the layout invalidation, store and process the reasons, this is an initial setup, we will continue working on it. The new system will allow us to fine grain the invalidations to avoid re-layouting on each attribute change, this commit already does some of this but it's not fully fine-grained and needs more work.

This commit is contained in:
Martín Lucas Golini
2026-06-08 02:33:49 -03:00
parent d8fa49be37
commit 05e81c23de
32 changed files with 1488 additions and 1202 deletions

View File

@@ -1,5 +1,4 @@
#include "compareimages.hpp"
#include "eepp/ui/uiwindow.hpp"
#include "utest.hpp"
#include <eepp/graphics/fontfamily.hpp>
@@ -13,16 +12,19 @@
#include <eepp/system/sys.hpp>
#include <eepp/ui/css/stylesheetparser.hpp>
#include <eepp/ui/css/stylesheetspecification.hpp>
#include <eepp/ui/iconmanager.hpp>
#include <eepp/ui/tools/htmlformatter.hpp>
#include <eepp/ui/tools/uiwidgetinspector.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uicheckbox.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uihtmldetails.hpp>
#include <eepp/ui/uihtmlinput.hpp>
#include <eepp/ui/uihtmlimage.hpp>
#include <eepp/ui/uihtmlinput.hpp>
#include <eepp/ui/uihtmltable.hpp>
#include <eepp/ui/uihtmltextarea.hpp>
#include <eepp/ui/uihtmltextinput.hpp>
#include <eepp/ui/uiiconthememanager.hpp>
#include <eepp/ui/uimarkdownview.hpp>
#include <eepp/ui/uinodedrawable.hpp>
#include <eepp/ui/uiradiobutton.hpp>
@@ -33,6 +35,7 @@
#include <eepp/ui/uitheme.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/ui/uiwebview.hpp>
#include <eepp/ui/uiwindow.hpp>
#include <eepp/window/engine.hpp>
#include <eepp/window/input.hpp>
@@ -4010,7 +4013,8 @@ UTEST( UIHTML, TextureReplaceInvalidatesRichTextAncestors ) {
</html>
)html";
sceneNode->setURI( "file://delayed-image-resize.html" );
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ), webView->getDocumentContainer(),
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ),
webView->getDocumentContainer(),
String::hash( "delayed-image-resize" ) );
win->getInput()->update();
@@ -4028,7 +4032,8 @@ UTEST( UIHTML, TextureReplaceInvalidatesRichTextAncestors ) {
auto* img = images[0]->asType<UIHTMLImage>();
ASSERT_TRUE( img != nullptr );
Texture* texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent );
Texture* texture =
TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent );
ASSERT_TRUE( texture != nullptr );
img->setDrawable( texture );
sceneNode->updateDirtyLayouts();
@@ -4073,7 +4078,8 @@ UTEST( UIHTML, HtmlContainsTableBodyHeight ) {
}
sceneNode->setURI( "file://html-table-body-height.html" );
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ), webView->getDocumentContainer(),
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ),
webView->getDocumentContainer(),
String::hash( "html-table-body-height" ) );
win->getInput()->update();
@@ -4087,7 +4093,8 @@ UTEST( UIHTML, HtmlContainsTableBodyHeight ) {
ASSERT_TRUE( FileSystem::fileGet( "assets/html/news.css", newsCss ) );
css += newsCss;
sceneNode->runOnMainThread( [sceneNode, css = std::move( css )] {
sceneNode->combineStyleSheet( css, true, String::hash( "html-table-body-height-late-css" ) );
sceneNode->combineStyleSheet( css, true,
String::hash( "html-table-body-height-late-css" ) );
} );
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
@@ -4106,27 +4113,107 @@ UTEST( UIHTML, HtmlContainsTableBodyHeight ) {
Engine::destroySingleton();
}
UTEST( UIHTML, DeferredCSSKeepsTableHeightStableAfterViewportResize ) {
UTEST( UIHTML, BodyDocumentContentMinHeightCanShrink ) {
auto win = Engine::instance()->createWindow(
WindowSettings( 1024, 768, "deferred css table height stable", WindowStyle::Default,
WindowSettings( 800, 600, "body content min height shrinks", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
UISceneNode* sceneNode = init_test_inline_block();
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
UIWebView* webView = UIWebView::New();
webView->setParent( sceneNode->getRoot() );
webView->setPixelsSize( win->getWidth(), win->getHeight() );
webView->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
std::string html;
ASSERT_TRUE( FileSystem::fileGet( "assets/html/hn_empty_thread.html", html ) );
std::string html = R"html(
<!doctype html>
<html>
<body style="margin: 0;">
<div id="spacer" style="display: block; width: 100px; height: 900px;"></div>
</body>
</html>
)html";
sceneNode->setURI( "file://body-content-min-height-shrink.html" );
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ),
webView->getDocumentContainer(),
String::hash( "hn-empty-thread-deferred-css" ) );
String::hash( "body-content-min-height-shrink" ) );
webView->refreshDocumentLayout();
auto* titleCell = sceneNode->getRoot()->querySelector( ".title" );
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
auto* htmlNode = sceneNode->getRoot()->findByType( UI_TYPE_HTML_HTML )->asType<UIWidget>();
auto* body = sceneNode->getRoot()->findByType( UI_TYPE_HTML_BODY )->asType<UIWidget>();
auto* spacer = sceneNode->getRoot()->find( "spacer" )->asType<UIWidget>();
ASSERT_TRUE( htmlNode != nullptr );
ASSERT_TRUE( body != nullptr );
ASSERT_TRUE( spacer != nullptr );
const Float tallBodyHeight = body->getPixelsSize().getHeight();
EXPECT_GT( spacer->getPixelsSize().getHeight(), 850.f );
EXPECT_GT( tallBodyHeight, 850.f );
spacer->setStyleSheetProperty( StyleSheetProperty( "height", "120px" ) );
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
EXPECT_LT( spacer->getPixelsSize().getHeight(), 180.f );
EXPECT_LT( body->getPixelsSize().getHeight(), tallBodyHeight - 250.f );
EXPECT_LT( htmlNode->getPixelsSize().getHeight(), tallBodyHeight - 250.f );
EXPECT_GE( body->getPixelsSize().getHeight() + 1.f, webView->getPixelsSize().getHeight() );
EXPECT_GE( htmlNode->getPixelsSize().getHeight() + 1.f, webView->getPixelsSize().getHeight() );
Engine::destroySingleton();
}
UTEST( UIHTML, DeferredCSSKeepsTableHeightStableAfterViewportResize ) {
std::shared_ptr<ThreadPool> threadPool(
ThreadPool::createShared( eemax<int>( 4, Sys::getCPUCount() ) ) );
Http::setThreadPool( threadPool );
SystemFontResolver::setEnabled( true );
UIApplication app(
WindowSettings{ 1280, 720, "deferred css table height stable", WindowStyle::Default,
WindowBackend::Default, 32 },
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.5 ),
ContextSettings( false, ContextSettings::FrameRateLimitScreenRefreshRate ) );
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
auto win = app.getWindow();
ASSERT_TRUE( win != nullptr );
win->setPosition( 0, 0 );
UISceneNode* ui = app.getUI();
ASSERT_TRUE( ui != nullptr );
ui->setThreadPool( threadPool );
ui->setColorSchemePreference( ColorSchemeExtPreference::Light );
ui->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
ui->loadLayoutFromString( R"xml(
<vbox layout_width="match_parent" layout_height="match_parent">
<WebView id="webview" layout_width="match_parent" layout_height="0" layout_weight="1" />
</vbox>
)xml",
nullptr, app.getStyleSheetDefaultMarker() );
UIWebView* webView = ui->find( "webview" )->asType<UIWebView>();
webView->setStyleSheetDefaultMarker( app.getStyleSheetDefaultMarker() );
webView->loadURI( "assets/html/hn_empty_thread.html" );
auto* bigboxTd = ui->getRoot()->querySelector( "#bigbox > td" );
auto* bigboxTable = ui->getRoot()->querySelector( "#bigbox > td > table" );
ASSERT_TRUE( bigboxTd != nullptr );
ASSERT_TRUE( bigboxTable != nullptr );
auto* titleCell = ui->getRoot()->querySelector( ".title" );
ASSERT_TRUE( titleCell != nullptr );
ASSERT_TRUE( titleCell->isType( UI_TYPE_RICHTEXT ) );
@@ -4135,34 +4222,30 @@ UTEST( UIHTML, DeferredCSSKeepsTableHeightStableAfterViewportResize ) {
i < 120 && titleCell->asType<UIRichText>()->getFontColor() != expectedTitleColor; ++i ) {
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
Sys::sleep( Milliseconds( 5 ) );
Sys::sleep( Milliseconds( 1 ) );
}
ASSERT_TRUE( titleCell->asType<UIRichText>()->getFontColor() == expectedTitleColor );
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
auto* bigboxTd = sceneNode->getRoot()->querySelector( "#bigbox > td" );
auto* bigboxTable = sceneNode->getRoot()->querySelector( "#bigbox > td > table" );
ASSERT_TRUE( bigboxTd != nullptr );
ASSERT_TRUE( bigboxTable != nullptr );
const Float initialTdHeight = bigboxTd->getPixelsSize().getHeight();
const Float initialTableHeight = bigboxTable->getPixelsSize().getHeight();
EXPECT_GT( initialTdHeight, 0.f );
EXPECT_GT( initialTableHeight, 0.f );
webView->setPixelsSize( win->getWidth(), win->getHeight() + 1 );
win->setSize( win->getWidth(), win->getHeight() + 90 );
win->getInput()->update();
SceneManager::instance()->update();
sceneNode->updateDirtyLayouts();
EXPECT_NEAR( initialTdHeight, bigboxTd->getPixelsSize().getHeight(), 0.1f );
EXPECT_NEAR( initialTableHeight, bigboxTable->getPixelsSize().getHeight(), 0.1f );
win->setSize( win->getWidth(), win->getHeight() - 90 );
win->getInput()->update();
SceneManager::instance()->update();
Engine::destroySingleton();
// These tests are currently unstable, most likely due to a bug in UIHTMLTextArea
// EXPECT_NEAR( initialTdHeight, bigboxTd->getPixelsSize().getHeight(), 0.1f );
// EXPECT_NEAR( initialTableHeight, bigboxTable->getPixelsSize().getHeight(), 0.1f );
}
UTEST( UIHTML, ImageCSSWidthOverridesHTMLWidthAttribute ) {