From 74b0454721bbd80ba0dc2936cc8dcb775afdf68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 5 Aug 2026 20:53:13 -0300 Subject: [PATCH] Refactor CSS properties to use dense IDs and bitsets: Introduce contiguous PropertyId and ShorthandId enums backed by name-to-ID maps, replacing hash-valued property identifiers throughout the style system. Replace PropertyIdSet's unordered set with a fixed-size bitset, eliminating heap allocations during style state changes while providing deterministic ascending iteration. Keep name hashes for stylesheet storage and unknown or custom properties, while using dense typed IDs for registered-property lookup and dispatch. Preserve property aliases and the public Checked compatibility identifier. Add transaction-end reconciliation for interdependent image sizing properties, deferring percentage-based calculations until their containing block is resolved. Keep formatting-context-owned sizes intact for table cells and inline content. Add validation and tests for dense built-in registration, custom IDs, aliases, bitset operations, allocation-free storage, unknown properties, property application order, and image sizing regressions. --- bin/unit_tests/assets/html/flex_form.html | 1 + bin/unit_tests/assets/html/image_width_3.html | 1 + include/eepp/ui/css/elementdefinition.hpp | 4 +- include/eepp/ui/css/idnamemap.hpp | 217 ++++ include/eepp/ui/css/propertydefinition.hpp | 327 +---- include/eepp/ui/css/propertyids.hpp | 392 ++++++ include/eepp/ui/css/propertyidset.hpp | 126 +- include/eepp/ui/css/propertyspecification.hpp | 40 +- include/eepp/ui/css/shorthanddefinition.hpp | 41 +- include/eepp/ui/css/stylesheetproperty.hpp | 26 +- .../eepp/ui/css/stylesheetspecification.hpp | 24 +- include/eepp/ui/css/stylesheetstyle.hpp | 4 +- include/eepp/ui/models/csspropertiesmodel.hpp | 2 +- include/eepp/ui/uihtmlimage.hpp | 7 + include/eepp/ui/uistyle.hpp | 10 +- include/eepp/ui/uiwidget.hpp | 15 + src/eepp/ui/css/elementdefinition.cpp | 20 +- src/eepp/ui/css/propertydefinition.cpp | 9 +- src/eepp/ui/css/propertyspecification.cpp | 179 ++- src/eepp/ui/css/shorthanddefinition.cpp | 11 +- src/eepp/ui/css/stylesheetproperty.cpp | 18 +- src/eepp/ui/css/stylesheetselectorrule.cpp | 2 +- src/eepp/ui/css/stylesheetspecification.cpp | 1085 ++++++++++------- src/eepp/ui/css/stylesheetstyle.cpp | 32 +- src/eepp/ui/uicheckbox.cpp | 2 - src/eepp/ui/uihtmlimage.cpp | 63 +- src/eepp/ui/uihtmlinput.cpp | 2 - src/eepp/ui/uihtmlwidget.cpp | 5 - src/eepp/ui/uimenucheckbox.cpp | 2 - src/eepp/ui/uistyle.cpp | 62 +- src/eepp/ui/uiviewpager.cpp | 12 +- src/eepp/ui/uiwidget.cpp | 11 +- src/tests/unit_tests/propertyidset_tests.cpp | 205 +++- .../unit_tests/uicss_inheritance_tests.cpp | 31 +- src/tests/unit_tests/uihtml_tests.cpp | 159 ++- 35 files changed, 2140 insertions(+), 1007 deletions(-) create mode 100644 include/eepp/ui/css/idnamemap.hpp create mode 100644 include/eepp/ui/css/propertyids.hpp diff --git a/bin/unit_tests/assets/html/flex_form.html b/bin/unit_tests/assets/html/flex_form.html index 8bd2b9093..f4107cdb3 100644 --- a/bin/unit_tests/assets/html/flex_form.html +++ b/bin/unit_tests/assets/html/flex_form.html @@ -4,6 +4,7 @@ +
+ + )html"; + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + + const Sizef base = applyImageSizingOrder( sceneNode, { "width", "height", "max-width" } ); + EXPECT_GT( base.getWidth(), 0.f ); + EXPECT_GT( base.getHeight(), 0.f ); + // max-width:100% constrains the image to the 800px containing block, and + // height:auto keeps the 1436/2560 aspect ratio. + EXPECT_LE( base.getWidth(), 800.f ); + EXPECT_NEAR( base.getHeight() / base.getWidth(), 1436.f / 2560.f, 0.01f ); + + const std::vector> orders = { + { "max-width", "height", "width" }, + { "height", "width", "max-width" }, + { "max-width", "width", "height" }, + }; + for ( const auto& order : orders ) { + const Sizef other = applyImageSizingOrder( sceneNode, order ); + EXPECT_NEAR( other.getWidth(), base.getWidth(), 1.f ); + EXPECT_NEAR( other.getHeight(), base.getHeight(), 1.f ); + } + + Engine::destroySingleton(); +} + +UTEST( UIHTML, ImageHeightAutoMaxWidthStyleStateTransition ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 1024, 768, "img style state transition", 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/" ); + + const std::string html = R"html( + + + +
+ + )html"; + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + + auto images = sceneNode->getRoot()->findAllByTag( "img" ); + ASSERT_EQ( images.size(), (size_t)1 ); + auto* img = images[0]->asType(); + ASSERT_TRUE( img != nullptr ); + + // HTML attributes alone keep the fixed 2560x1436 box. + EXPECT_NEAR( img->getPixelsSize().getWidth(), 2560.f, 1.f ); + EXPECT_NEAR( img->getPixelsSize().getHeight(), 1436.f, 1.f ); + + const std::string rule = "img { height: auto; max-width: 100%; }"; + + // Apply height:auto + max-width:100% through a style state change. + sceneNode->setStyleSheet( rule ); + sceneNode->updateDirtyLayouts(); + const Float constrainedWidth = img->getPixelsSize().getWidth(); + const Float constrainedHeight = img->getPixelsSize().getHeight(); + EXPECT_LE( constrainedWidth, 800.f ); + EXPECT_NEAR( constrainedHeight / constrainedWidth, 1436.f / 2560.f, 0.01f ); + + // Remove the rule, then reapply it: the final geometry must be identical. + sceneNode->setStyleSheet( CSS::StyleSheet() ); + sceneNode->updateDirtyLayouts(); + sceneNode->setStyleSheet( rule ); + sceneNode->updateDirtyLayouts(); + EXPECT_NEAR( img->getPixelsSize().getWidth(), constrainedWidth, 1.f ); + EXPECT_NEAR( img->getPixelsSize().getHeight(), constrainedHeight, 1.f ); + + Engine::destroySingleton(); +}