diff --git a/.agent/plans/eepp_superluminal_gui_html_optimization_plan_2026_07_09.md b/.agent/plans/eepp_superluminal_gui_html_optimization_plan_2026_07_09.md index 22f5f205d..acdc41121 100644 --- a/.agent/plans/eepp_superluminal_gui_html_optimization_plan_2026_07_09.md +++ b/.agent/plans/eepp_superluminal_gui_html_optimization_plan_2026_07_09.md @@ -10,6 +10,21 @@ Session analyzed: /home/downloads/2026-07-09_19-52-19_eepp-unit_tests.slp ``` +Current baseline capture after CSS selector phases 1-8: + +```text +/tmp/eepp-unit-tests-current-final-2026-07-10.linux +``` + +Accessor-inlining comparison capture: + +```text +/tmp/eepp-unit-tests-inline-accessors-2026-07-10.linux +``` + +Both current captures ran all 737 release tests at 500 Hz. The post-change run passed 736 tests +with one skipped test. + MCP could not open the packaged `.slp` directly, so it was extracted to `/tmp/superluminal-eepp-unit-tests` and the contained `.session` was queried. ## Capture Notes @@ -108,6 +123,33 @@ Validation: ### 3. Fast-parse common CSS lengths without allocations +**Status: Implemented and measured** + +`StyleSheetLength::fromString()` now trims once, scans scalar numeric prefixes directly from a +`std::string_view`, and converts the numeric subview through `String::fromString()`. The core string +API exposes `std::string_view` numeric overloads while retaining exact `std::string` forwarding +overloads for source and ABI compatibility with the implicitly constructible `EE::String` type. +The CSS parser no longer builds separate number/unit strings or retries parsing by removing +characters. Function expressions retain the existing parser, and position keywords map directly +to percentage lengths without recursive string construction. + +Validation includes signed values, leading-dot decimals, scientific notation, surrounding +whitespace, position keywords, unitless and unknown units, `pxAsDp`, all existing CSS function +tests, and a dedicated release benchmark. + +- Final focused benchmark median for 320,000 mixed values: 13.67 ms to 10.10 ms, a 26.1% + reduction. +- Before numeric conversion was centralized in `String`, the full-suite capture measured 105.4 ms + inclusive / 44.6 ms exclusive to 46.5 ms / 12.4 ms. A future full capture should refresh those + aggregate numbers for the final implementation. +- Post-change full release suite: 737 passed, one skipped. + +Comparison capture: + +```text +/tmp/eepp-unit-tests-length-fast-path-2026-07-10.linux +``` + Files: ```text @@ -305,6 +347,25 @@ Validation: ### 10. Broaden hot accessor/type-check inlining beyond selector code +**Status: Core hierarchy implemented and measured** + +The trivial `getType()` / `isType()` implementations for `Node`, `UINode`, `UIWidget`, +`UILayout`, `UIHTMLWidget`, and `UIRichText` are now inline, along with `Node::isLayout()`. + +In the comparable full-suite captures: + +- `UISceneNode::invalidateLayout()` decreased from 86.1 ms inclusive / 41.5 ms exclusive to + 26.3 ms total. +- Base type accessor symbols largely disappeared from the hot function list. +- `getEffectiveWhiteSpaceCollapse()` decreased from 344.0 ms to 162.9 ms inclusive, partly + because its repeated type checks became cheaper. +- Unit-test process CPU time decreased from 20.12 s to 19.88 s, approximately 1.2%. Treat this + whole-process result as directional because the full suite contains rendering and image-diff + noise. + +Further subclass inlining remains possible, especially table widgets, but the next independent +high-value target is CSS length parsing. + Files: ```text diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 42b93bd04..b286a0402 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -539,6 +540,18 @@ class EE_API String { static bool fromString( float& t, const std::string& s ); static bool fromString( double& t, const std::string& s ); + /** Converts from a string view to type */ + static bool fromString( Int8& t, std::string_view s, int base = 10 ); + static bool fromString( Int16& t, std::string_view s, int base = 10 ); + static bool fromString( Int32& t, std::string_view s, int base = 10 ); + static bool fromString( Int64& t, std::string_view s, int base = 10 ); + static bool fromString( Uint8& t, std::string_view s, int base = 10 ); + static bool fromString( Uint16& t, std::string_view s, int base = 10 ); + static bool fromString( Uint32& t, std::string_view s, int base = 10 ); + static bool fromString( Uint64& t, std::string_view s, int base = 10 ); + static bool fromString( float& t, std::string_view s ); + static bool fromString( double& t, std::string_view s ); + /** Converts from a String to type */ static bool fromString( Int8& t, const String& s, int base = 10 ); static bool fromString( Int16& t, const String& s, int base = 10 ); diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 56ac4a219..03488f2fb 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -202,7 +202,7 @@ class EE_API Node : public Transformable { * * @return The node type as a Uint32. */ - virtual Uint32 getType() const; + virtual Uint32 getType() const { return 0; } /** * @brief Checks if the node is of a specific type. @@ -212,7 +212,7 @@ class EE_API Node : public Transformable { * @param type The type identifier to check. * @return True if the node is of the specified type, false otherwise. */ - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { return Node::getType() == type; } /** @return True if this node is a UITextNode, false otherwise. */ inline bool isTextNode() const { return 0 != ( mNodeFlags & NODE_FLAG_TEXTNODE ); } @@ -279,7 +279,7 @@ class EE_API Node : public Transformable { * * @return The size as a Sizef. */ - virtual const Sizef& getSize() const; + virtual const Sizef& getSize() const { return mSize; } /** * @brief Gets the node size in actual screen pixels. @@ -289,7 +289,7 @@ class EE_API Node : public Transformable { * * @return The pixel size as a Sizef. */ - const Sizef& getPixelsSize() const; + inline const Sizef& getPixelsSize() const { return mSize; } /** * @brief Sets the visibility of the node. @@ -322,7 +322,7 @@ class EE_API Node : public Transformable { * * @return True if the node is visible, false otherwise. */ - bool isVisible() const; + inline bool isVisible() const { return mVisible; } /** * @brief Checks if the node and all its parents are visible. @@ -350,7 +350,7 @@ class EE_API Node : public Transformable { * * @return True if the node is enabled, false if disabled. */ - bool isEnabled() const; + inline bool isEnabled() const { return mEnabled; } /** * @brief Checks if the node is disabled. @@ -359,7 +359,7 @@ class EE_API Node : public Transformable { * * @return True if the node is disabled, false otherwise. */ - bool isDisabled() const; + inline bool isDisabled() const { return !mEnabled; } /** * @brief Gets the parent node. @@ -468,7 +468,7 @@ class EE_API Node : public Transformable { * * @return The stored user data pointer. */ - const UintPtr& getData() const; + inline const UintPtr& getData() const { return mData; } /** * @brief Sets the blend mode for this node. @@ -544,7 +544,7 @@ class EE_API Node : public Transformable { * * @return True if this node is a SceneNode, false otherwise. */ - bool isSceneNode() const; + inline bool isSceneNode() const { return 0 != ( mNodeFlags & NODE_FLAG_SCENENODE ); } /** * @brief Checks if this node is a UISceneNode. @@ -553,7 +553,7 @@ class EE_API Node : public Transformable { * * @return True if this node is a UISceneNode, false otherwise. */ - bool isUISceneNode() const; + inline bool isUISceneNode() const { return 0 != ( mNodeFlags & NODE_FLAG_UISCENENODE ); } /** * @brief Checks if this node is a UINode. @@ -562,7 +562,7 @@ class EE_API Node : public Transformable { * * @return True if this node is a UINode, false otherwise. */ - bool isUINode() const; + inline bool isUINode() const { return 0 != ( mNodeFlags & NODE_FLAG_UINODE ); } /** * @brief Checks if this node is a UIWidget. @@ -580,7 +580,7 @@ class EE_API Node : public Transformable { * * @return True if this node is a Window, false otherwise. */ - bool isWindow() const; + inline bool isWindow() const { return 0 != ( mNodeFlags & NODE_FLAG_WINDOW ); } /** * @brief Checks if this node is a Layout. @@ -590,7 +590,7 @@ class EE_API Node : public Transformable { * * @return True if this node is a Layout, false otherwise. */ - bool isLayout() const; + inline bool isLayout() const { return 0 != ( mNodeFlags & NODE_FLAG_LAYOUT ); } /** * @brief Checks if clipping is enabled for this node. @@ -599,21 +599,21 @@ class EE_API Node : public Transformable { * * @return True if clipping is enabled, false otherwise. */ - bool isClipped() const; + inline bool isClipped() const { return 0 != ( mNodeFlags & NODE_FLAG_CLIP_ENABLE ); } /** * @brief Checks if this node has rotation applied. * * @return True if the node's rotation is non-zero, false otherwise. */ - bool isRotated() const; + inline bool isRotated() const { return 0 != ( mNodeFlags & NODE_FLAG_ROTATED ); } /** * @brief Checks if this node has scaling applied. * * @return True if the node's scale is not (1,1), false otherwise. */ - bool isScaled() const; + inline bool isScaled() const { return 0 != ( mNodeFlags & NODE_FLAG_SCALED ); } /** * @brief Checks if this node uses a frame buffer. @@ -622,21 +622,23 @@ class EE_API Node : public Transformable { * * @return True if using frame buffer rendering, false otherwise. */ - bool isFrameBuffer() const; + inline bool isFrameBuffer() const { return 0 != ( mNodeFlags & NODE_FLAG_FRAME_BUFFER ); } /** * @brief Checks if the mouse is currently over this node. * * @return True if the mouse cursor is over this node, false otherwise. */ - bool isMouseOver() const; + inline bool isMouseOver() const { return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER ); } /** * @brief Checks if the mouse is over this node or any of its children. * * @return True if the mouse is over this node or any descendant, false otherwise. */ - bool isMouseOverMeOrChildren() const; + inline bool isMouseOverMeOrChildren() const { + return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER_ME_OR_CHILD ); + } /** * @brief Checks if this node and all its parents are visible in the tree. @@ -756,14 +758,14 @@ class EE_API Node : public Transformable { * * @return Pointer to the first child or nullptr if no children exist. */ - Node* getFirstChild() const; + inline Node* getFirstChild() const { return mChild; } /** * @brief Gets the last child node. * * @return Pointer to the last child or nullptr if no children exist. */ - Node* getLastChild() const; + inline Node* getLastChild() const { return mChildLast; } /** * @brief Gets the world polygon of this node. @@ -873,7 +875,7 @@ class EE_API Node : public Transformable { * * @return The ID hash value. */ - const String::HashType& getIdHash() const; + inline const String::HashType& getIdHash() const { return mIdHash; } /** * @brief Finds a descendant node by its ID string. @@ -1030,7 +1032,7 @@ class EE_API Node : public Transformable { * * @return True if reverse drawing is enabled, false otherwise. */ - bool isReverseDraw() const; + inline bool isReverseDraw() const { return 0 != ( mNodeFlags & NODE_FLAG_REVERSE_DRAW ); } /** * @brief Enables or disables reverse drawing order. @@ -1428,7 +1430,7 @@ class EE_API Node : public Transformable { * * @return True if this node has focus, false otherwise. */ - bool hasFocus() const; + inline bool hasFocus() const { return 0 != ( mNodeFlags & NODE_FLAG_HAS_FOCUS ); } /** * @brief Checks if this node or any descendant has focus. @@ -1553,7 +1555,7 @@ class EE_API Node : public Transformable { * * @return Pointer to the SceneNode or nullptr if not found. */ - SceneNode* getSceneNode() const; + inline SceneNode* getSceneNode() const { return mSceneNode; } /** * @brief Gets the event dispatcher associated with this node. @@ -1764,7 +1766,7 @@ class EE_API Node : public Transformable { * * @return True if the node's loading flag is set, false otherwise. */ - bool isLoadingState() const; + inline bool isLoadingState() const { return 0 != ( mNodeFlags & NODE_FLAG_LOADING ); } /** * @brief Called when the node's ID changes. @@ -1779,7 +1781,7 @@ class EE_API Node : public Transformable { * * @return True if the close flag is set (node will be removed), false otherwise. */ - bool isClosing() const; + inline bool isClosing() const { return 0 != ( mNodeFlags & NODE_FLAG_CLOSE ); } /** * @brief Checks if the node is marked for closure or any node in its parent tree. @@ -1793,7 +1795,9 @@ class EE_API Node : public Transformable { * * @return True if the closing children flag is set, false otherwise. */ - bool isClosingChildren() const; + inline bool isClosingChildren() const { + return 0 != ( mNodeFlags & NODE_FLAG_CLOSING_CHILDREN ); + } /** * @brief Finds the node under a point, considering hit testing. @@ -2554,7 +2558,9 @@ class EE_API Node : public Transformable { * * @return True if NODE_FLAG_SCHEDULED_UPDATE is set. */ - bool isSubscribedForScheduledUpdate(); + inline bool isSubscribedForScheduledUpdate() { + return 0 != ( mNodeFlags & NODE_FLAG_SCHEDULED_UPDATE ); + } }; }} // namespace EE::Scene diff --git a/include/eepp/ui/uihtmlwidget.hpp b/include/eepp/ui/uihtmlwidget.hpp index d71bfba39..a4ccb8a81 100644 --- a/include/eepp/ui/uihtmlwidget.hpp +++ b/include/eepp/ui/uihtmlwidget.hpp @@ -53,9 +53,11 @@ class EE_API UIHTMLWidget : public UILayout { virtual ~UIHTMLWidget(); - virtual Uint32 getType() const; + virtual Uint32 getType() const { return UI_TYPE_HTML_WIDGET; } - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { + return UIHTMLWidget::getType() == type || UILayout::isType( type ); + } UILayouter* getLayouter(); @@ -67,9 +69,13 @@ class EE_API UIHTMLWidget : public UILayout { void setDisplay( CSSDisplay display ); - bool isFlex() const; + inline bool isFlex() const { + return mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::InlineFlex; + } - bool isGrid() const; + inline bool isGrid() const { + return mDisplay == CSSDisplay::Grid || mDisplay == CSSDisplay::InlineGrid; + } CSSPosition getCSSPosition() const { return mPosition; } @@ -317,7 +323,9 @@ class EE_API UIHTMLWidget : public UILayout { virtual void invalidateIntrinsicSize(); - bool isOutOfFlow() const; + inline bool isOutOfFlow() const { + return mPosition == CSSPosition::Absolute || mPosition == CSSPosition::Fixed; + } bool establishesBlockFormattingContext() const; diff --git a/include/eepp/ui/uilayout.hpp b/include/eepp/ui/uilayout.hpp index 4989746c2..d6d36081c 100644 --- a/include/eepp/ui/uilayout.hpp +++ b/include/eepp/ui/uilayout.hpp @@ -18,9 +18,11 @@ class EE_API UILayout : public UIWidget { Uint64 treeUpdates{ 0 }; }; - virtual Uint32 getType() const; + virtual Uint32 getType() const { return UI_TYPE_LAYOUT; } - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { + return UILayout::getType() == type || UIWidget::isType( type ); + } virtual const Sizef& getSize() const; diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index a0ae5e8d9..f78228dd3 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -117,7 +117,7 @@ class EE_API UINode : public Node { * * @return The node type as a Uint32. */ - virtual Uint32 getType() const; + virtual Uint32 getType() const { return UI_TYPE_UINODE; } /** * @brief Checks if the node is of a specific type. @@ -127,7 +127,9 @@ class EE_API UINode : public Node { * @param type The type identifier to check. * @return True if the node is of the specified type, false otherwise. */ - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { + return UINode::getType() == type || Node::isType( type ); + } /** * @brief Sets the node position in density-independent pixels (dp). diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp index 05071a27e..4d16b809a 100644 --- a/include/eepp/ui/uirichtext.hpp +++ b/include/eepp/ui/uirichtext.hpp @@ -68,9 +68,11 @@ class EE_API UIRichText : public UIHTMLWidget { static UIRichText* NewBlockquote() { return UIRichText::NewWithTag( "blockquote" ); }; - virtual Uint32 getType() const; + virtual Uint32 getType() const { return UI_TYPE_RICHTEXT; } - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { + return UIRichText::getType() == type || UIHTMLWidget::isType( type ); + } virtual void draw(); diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 24d105537..fb9db77ff 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -80,7 +80,7 @@ class EE_API UIWidget : public UINode { * * @return The widget type as a Uint32. */ - virtual Uint32 getType() const; + virtual Uint32 getType() const { return UI_TYPE_WIDGET; } /** * @brief Checks if the widget is of a specific type. @@ -90,7 +90,9 @@ class EE_API UIWidget : public UINode { * @param type The type identifier to check. * @return True if the widget is of the specified type, false otherwise. */ - virtual bool isType( const Uint32& type ) const; + virtual bool isType( const Uint32& type ) const { + return UIWidget::getType() == type || UINode::isType( type ); + } /** * @brief Sets multiple flags on the widget. diff --git a/src/benchmarks/inline_layout_benchmark.cpp b/src/benchmarks/inline_layout_benchmark.cpp index 8e79383e8..97952c155 100644 --- a/src/benchmarks/inline_layout_benchmark.cpp +++ b/src/benchmarks/inline_layout_benchmark.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -199,6 +200,28 @@ UTEST( Benchmark, CSSAttributeSelectorMatching ) { Engine::destroySingleton(); } +UTEST( Benchmark, CSSLengthParsing ) { + const std::array values = { + "0", "12px", "-1.5em", "+24dp", "100%", ".75rem", "50vw", "25vh", + "2.54cm", "12pt", "1e3px", "center", "left", "10vmin", "4ch", "8dpr" }; + const int iterations = getSelectorMatchingIterations(); + Float parsedValueSum = 0; + Clock parsingClock; + for ( int iteration = 0; iteration < iterations; ++iteration ) { + for ( const auto& value : values ) { + const auto length = StyleSheetLength::fromString( value ); + parsedValueSum += length.getValue(); + } + } + const Time parsingElapsed = parsingClock.getElapsedTime(); + + EXPECT_TRUE( parsedValueSum > 0 ); + UTEST_PRINT_INFO( + String::format( "CSS length parsing: %lld us", parsingElapsed.asMicroseconds() ).c_str() ); + UTEST_PRINT_INFO( + String::format( "CSS length values: %d", iterations * values.size() ).c_str() ); +} + static int getMarkdownFlushIterations() { if ( const char* env = std::getenv( "EE_MARKDOWN_BENCH_FLUSH_ITERATIONS" ) ) { Int32 val = markdownFlushIterations; diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 0cf5f6d9e..e11573d61 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -54,7 +54,7 @@ namespace EE { -template static bool _fromString( T& t, const std::string& s, int base = 10 ) { +template static bool _fromString( T& t, std::string_view s, int base = 10 ) { const char* begin = s.data(); const char* end = s.data() + s.size(); @@ -109,83 +109,123 @@ template static std::string _toString( const T& value, size_t digitsAf } bool String::fromString( Int8& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Int16& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Int32& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Int64& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Uint8& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Uint16& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Uint32& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( Uint64& t, const std::string& s, int base ) { - return _fromString<>( t, s, base ); + return fromString( t, std::string_view( s ), base ); } bool String::fromString( float& t, const std::string& s ) { - return _fromString<>( t, s ); + return fromString( t, std::string_view( s ) ); } bool String::fromString( double& t, const std::string& s ) { + return fromString( t, std::string_view( s ) ); +} + +bool String::fromString( Int8& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Int16& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Int32& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Int64& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Uint8& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Uint16& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Uint32& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( Uint64& t, std::string_view s, int base ) { + return _fromString<>( t, s, base ); +} + +bool String::fromString( float& t, std::string_view s ) { + return _fromString<>( t, s ); +} + +bool String::fromString( double& t, std::string_view s ) { return _fromString<>( t, s ); } bool String::fromString( Int8& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Int16& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Int32& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Int64& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Uint8& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Uint16& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Uint32& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( Uint64& t, const String& s, int base ) { - return _fromString<>( t, s, base ); + return _fromString<>( t, s.toUtf8(), base ); } bool String::fromString( float& t, const String& s ) { - return _fromString<>( t, s ); + return _fromString<>( t, s.toUtf8() ); } bool String::fromString( double& t, const String& s ) { - return _fromString<>( t, s ); + return _fromString<>( t, s.toUtf8() ); } std::string String::toString( const Int8& i ) { diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index bddc1ad30..7e471cf9b 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -96,14 +96,6 @@ void Node::nodeToWorldTranslation( Vector2f& Pos ) const { } } -Uint32 Node::getType() const { - return 0; -} - -bool Node::isType( const Uint32& type ) const { - return Node::getType() == type; -} - void Node::messagePost( const NodeMessage* Msg ) { Node* node = this; while ( NULL != node ) { @@ -164,14 +156,6 @@ void Node::setInternalHeight( const Float& height ) { setInternalSize( Sizef( getSize().getWidth(), height ) ); } -const Sizef& Node::getSize() const { - return mSize; -} - -const Sizef& Node::getPixelsSize() const { - return mSize; -} - Node* Node::setVisible( const bool& visible, bool emitEventNotification ) { if ( mVisible != visible ) { mVisible = visible; @@ -197,10 +181,6 @@ Node* Node::setChildrenVisibility( bool visible, bool emitEventNotification ) { return this; } -bool Node::isVisible() const { - return mVisible; -} - bool Node::hasVisibility() const { const Node* cur = this; while ( cur ) { @@ -219,14 +199,6 @@ Node* Node::setEnabled( const bool& enabled ) { return this; } -bool Node::isEnabled() const { - return mEnabled; -} - -bool Node::isDisabled() const { - return !mEnabled; -} - void Node::updateDrawInvalidator( bool force ) { mNodeDrawInvalidator = getDrawInvalidator(); @@ -253,10 +225,6 @@ void Node::unsubscribeScheduledUpdate() { } } -bool Node::isSubscribedForScheduledUpdate() { - return 0 != ( mNodeFlags & NODE_FLAG_SCHEDULED_UPDATE ); -} - Node* Node::setParent( Node* parent ) { eeASSERT( NULL != parent ); @@ -370,14 +338,6 @@ Uint32 Node::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) { return 1; } -bool Node::isMouseOver() const { - return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER ); -} - -bool Node::isMouseOverMeOrChildren() const { - return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER_ME_OR_CHILD ); -} - Uint32 Node::onMouseDoubleClick( const Vector2i& Pos, const Uint32& Flags ) { sendMouseEvent( Event::MouseDoubleClick, Pos, Flags ); return 1; @@ -442,10 +402,6 @@ Node* Node::setData( const UintPtr& data ) { return this; } -const UintPtr& Node::getData() const { - return mData; -} - Node* Node::setBlendMode( const BlendMode& blend ) { mBlend = blend; invalidateDraw(); @@ -755,10 +711,6 @@ void Node::onIdChange() { sendCommonEvent( Event::OnIdChange ); } -bool Node::isClosing() const { - return 0 != ( mNodeFlags & NODE_FLAG_CLOSE ); -} - bool Node::inClosingTree() const { if ( isClosing() ) return true; @@ -771,14 +723,6 @@ bool Node::inClosingTree() const { return false; } -bool Node::isClosingChildren() const { - return 0 != ( mNodeFlags & NODE_FLAG_CLOSING_CHILDREN ); -} - -const String::HashType& Node::getIdHash() const { - return mIdHash; -} - static bool isNestedSceneBoundary( const Node* child ) { return child->isSceneNode() && child->getSceneNode() == child; } @@ -933,10 +877,6 @@ void Node::setLoadingState( bool loading ) { writeNodeFlag( NODE_FLAG_LOADING, loading ? 1 : 0 ); } -bool Node::isLoadingState() const { - return 0 != ( mNodeFlags & NODE_FLAG_LOADING ); -} - Uint32 Node::getChildCount() const { Node* child = mChild; Uint32 count = 0; @@ -1002,14 +942,6 @@ Uint32 Node::getNodeOfTypeIndex() const { return nodeIndex; } -Node* Node::getFirstChild() const { - return mChild; -} - -Node* Node::getLastChild() const { - return mChildLast; -} - Node* Node::overFind( const Vector2f& point ) { Node* pOver = NULL; @@ -1078,42 +1010,6 @@ void Node::onSceneChange() { } } -bool Node::isWindow() const { - return 0 != ( mNodeFlags & NODE_FLAG_WINDOW ); -} - -bool Node::isLayout() const { - return 0 != ( mNodeFlags & NODE_FLAG_LAYOUT ); -} - -bool Node::isClipped() const { - return 0 != ( mNodeFlags & NODE_FLAG_CLIP_ENABLE ); -} - -bool Node::isRotated() const { - return 0 != ( mNodeFlags & NODE_FLAG_ROTATED ); -} - -bool Node::isScaled() const { - return 0 != ( mNodeFlags & NODE_FLAG_SCALED ); -} - -bool Node::isFrameBuffer() const { - return 0 != ( mNodeFlags & NODE_FLAG_FRAME_BUFFER ); -} - -bool Node::isSceneNode() const { - return 0 != ( mNodeFlags & NODE_FLAG_SCENENODE ); -} - -bool Node::isUISceneNode() const { - return 0 != ( mNodeFlags & NODE_FLAG_UISCENENODE ); -} - -bool Node::isUINode() const { - return 0 != ( mNodeFlags & NODE_FLAG_UINODE ); -} - bool Node::isMeOrParentTreeVisible() const { const Node* node = this; while ( NULL != node ) { @@ -1342,10 +1238,6 @@ void Node::nodeToWorld( Vector2f& pos ) const { pos = Vector2f( toPos.x, toPos.y ); } -bool Node::isReverseDraw() const { - return 0 != ( mNodeFlags & NODE_FLAG_REVERSE_DRAW ); -} - void Node::setReverseDraw( bool reverseDraw ) { writeNodeFlag( NODE_FLAG_REVERSE_DRAW, reverseDraw ? 1 : 0 ); invalidateDraw(); @@ -1357,10 +1249,6 @@ void Node::invalidateDraw() { } } -SceneNode* Node::getSceneNode() const { - return mSceneNode; -} - SceneNode* Node::findSceneNode() { Node* node = mParentNode; while ( node != NULL ) { @@ -1747,10 +1635,6 @@ Uint32 Node::onFocusLoss() { return 1; } -bool Node::hasFocus() const { - return 0 != ( mNodeFlags & NODE_FLAG_HAS_FOCUS ); -} - bool Node::hasFocusWithin() const { return hasFocus() || inParentTreeOf( getEventDispatcher()->getFocusNode() ); } diff --git a/src/eepp/ui/css/stylesheetlength.cpp b/src/eepp/ui/css/stylesheetlength.cpp index f3fb975af..8413130c0 100644 --- a/src/eepp/ui/css/stylesheetlength.cpp +++ b/src/eepp/ui/css/stylesheetlength.cpp @@ -44,22 +44,6 @@ enum PercentagePositions : String::HashType { None = 0, }; -static std::string positionToPercentage( const PercentagePositions& pos ) { - switch ( pos ) { - case Center: - return "50%"; - case Left: - case Top: - return "0%"; - case Right: - case Bottom: - return "100%"; - default: - case None: - return ""; - } -} - static PercentagePositions isPercentagePosition( const String::HashType& strHash ) { switch ( strHash ) { case PercentagePositions::Center: @@ -76,6 +60,45 @@ static PercentagePositions isPercentagePosition( const String::HashType& strHash return PercentagePositions::None; } +static size_t numericPrefixLength( std::string_view value ) { + if ( value.empty() ) + return 0; + size_t pos = 0; + if ( value[pos] == '-' || value[pos] == '+' ) + pos++; + + bool hasDigit = false; + bool hasDot = false; + while ( pos < value.size() ) { + const char c = value[pos]; + if ( c >= '0' && c <= '9' ) { + hasDigit = true; + pos++; + } else if ( c == '.' && !hasDot ) { + hasDot = true; + pos++; + } else { + break; + } + } + + if ( !hasDigit ) + return 0; + + if ( pos < value.size() && ( value[pos] == 'e' || value[pos] == 'E' ) ) { + size_t expPos = pos + 1; + if ( expPos < value.size() && ( value[expPos] == '-' || value[expPos] == '+' ) ) + expPos++; + const size_t expDigits = expPos; + while ( expPos < value.size() && value[expPos] >= '0' && value[expPos] <= '9' ) + expPos++; + if ( expPos != expDigits ) + pos = expPos; + } + + return pos; +} + StyleSheetLength::Unit StyleSheetLength::unitFromString( std::string_view unitStr ) { switch ( String::hashToLower( unitStr ) ) { case UnitHashes::Percentage: @@ -187,41 +210,10 @@ bool StyleSheetLength::isLength( std::string_view unitStr ) { if ( isFunctionString( unitStr ) ) return true; - size_t pos = 0; - if ( unitStr[pos] == '-' || unitStr[pos] == '+' ) - pos++; - - bool hasDigit = false; - bool hasDot = false; - while ( pos < unitStr.size() ) { - char c = unitStr[pos]; - if ( c >= '0' && c <= '9' ) { - hasDigit = true; - pos++; - } else if ( c == '.' && !hasDot ) { - hasDot = true; - pos++; - } else { - break; - } - } - - if ( !hasDigit ) + const size_t pos = numericPrefixLength( unitStr ); + if ( pos == 0 ) return false; - if ( pos < unitStr.size() && ( unitStr[pos] == 'e' || unitStr[pos] == 'E' ) ) { - size_t expPos = pos + 1; - if ( expPos < unitStr.size() && ( unitStr[expPos] == '-' || unitStr[expPos] == '+' ) ) - expPos++; - bool hasExpDigit = false; - while ( expPos < unitStr.size() && unitStr[expPos] >= '0' && unitStr[expPos] <= '9' ) { - hasExpDigit = true; - expPos++; - } - if ( hasExpDigit ) - pos = expPos; - } - std::string_view unit = unitStr.substr( pos ); if ( unit.empty() ) return true; @@ -390,14 +382,11 @@ StyleSheetLength& StyleSheetLength::operator=( const StyleSheetLength& val ) { StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue, bool pxAsDp ) { - PercentagePositions isPercentage = isPercentagePosition( String::hashToLower( str ) ); - if ( PercentagePositions::None != isPercentage ) - return fromString( positionToPercentage( isPercentage ), defaultValue ); - StyleSheetLength length; length.setValue( defaultValue, Unit::Px ); + const std::string_view value = String::trim( std::string_view( str ) ); - if ( isFunctionString( str ) ) { + if ( isFunctionString( value ) ) { Unit funcUnit = Unit::Px; Arguments args; if ( parseFunction( str, funcUnit, args ) ) { @@ -409,30 +398,39 @@ StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Flo return length; } - std::string num; - std::string unit; - - for ( std::size_t i = 0; i < str.size(); i++ ) { - char c = str[i]; - if ( String::isNumber( c, true, true ) || ( '-' == c && i == 0 ) || - ( '+' == c && i == 0 ) ) { - num += c; + if ( !value.empty() ) { + const char first = value.front(); + const bool startsNumeric = + ( first >= '0' && first <= '9' ) || first == '.' || first == '-' || first == '+'; + if ( startsNumeric ) { + const size_t numberLength = numericPrefixLength( value ); + if ( numberLength != 0 ) { + std::string_view number = value.substr( 0, numberLength ); + if ( number.front() == '+' ) + number.remove_prefix( 1 ); + Float parsedValue = 0; + if ( String::fromString( parsedValue, number ) ) + length.setValue( parsedValue, unitFromString( value.substr( numberLength ) ) ); + } } else { - unit = str.substr( i ); - break; + switch ( isPercentagePosition( String::hashToLower( value ) ) ) { + case PercentagePositions::Center: + length.setValue( 50, Unit::Percentage ); + break; + case PercentagePositions::Right: + case PercentagePositions::Bottom: + length.setValue( 100, Unit::Percentage ); + break; + case PercentagePositions::Left: + case PercentagePositions::Top: + length.setValue( 0, Unit::Percentage ); + break; + case PercentagePositions::None: + break; + } } } - if ( !num.empty() ) { - Float val = 0; - while ( !num.empty() && !String::fromString( val, num ) ) { - unit = num.back() + unit; - num.pop_back(); - } - if ( !num.empty() ) - length.setValue( val, unitFromString( unit ) ); - } - if ( pxAsDp && length.getUnit() == Unit::Px ) length.mUnit = Unit::Dp; diff --git a/src/eepp/ui/uihtmltable.cpp b/src/eepp/ui/uihtmltable.cpp index 319e45975..bc0b95ef9 100644 --- a/src/eepp/ui/uihtmltable.cpp +++ b/src/eepp/ui/uihtmltable.cpp @@ -19,7 +19,6 @@ UIHTMLTable::UIHTMLTable() : UIHTMLWidget( "table" ) { Uint32 UIHTMLTable::getType() const { return UI_TYPE_HTML_TABLE; } - bool UIHTMLTable::isType( const Uint32& type ) const { return UIHTMLTable::getType() == type || UIHTMLWidget::isType( type ); } @@ -196,7 +195,6 @@ UIHTMLTableRow::UIHTMLTableRow() : UIHTMLWidget( "tr" ) { Uint32 UIHTMLTableRow::getType() const { return UI_TYPE_HTML_TABLE_ROW; } - bool UIHTMLTableRow::isType( const Uint32& type ) const { return UIHTMLTableRow::getType() == type || UIHTMLWidget::isType( type ); } @@ -214,7 +212,6 @@ UIHTMLTableCell::UIHTMLTableCell( const std::string& tag ) : UIRichText( tag ) { Uint32 UIHTMLTableCell::getType() const { return UI_TYPE_HTML_TABLE_CELL; } - bool UIHTMLTableCell::isType( const Uint32& type ) const { return UIHTMLTableCell::getType() == type || UIRichText::isType( type ); } @@ -292,7 +289,6 @@ UIHTMLTableHead::UIHTMLTableHead() : UIHTMLWidget( "thead" ) { Uint32 UIHTMLTableHead::getType() const { return UI_TYPE_HTML_TABLE_HEAD; } - bool UIHTMLTableHead::isType( const Uint32& type ) const { return UIHTMLTableHead::getType() == type || UIHTMLWidget::isType( type ); } @@ -310,7 +306,6 @@ UIHTMLTableBody::UIHTMLTableBody() : UIHTMLWidget( "tbody" ) { Uint32 UIHTMLTableBody::getType() const { return UI_TYPE_HTML_TABLE_BODY; } - bool UIHTMLTableBody::isType( const Uint32& type ) const { return UIHTMLTableBody::getType() == type || UIHTMLWidget::isType( type ); } @@ -328,7 +323,6 @@ UIHTMLTableFooter::UIHTMLTableFooter() : UIHTMLWidget( "tfoot" ) { Uint32 UIHTMLTableFooter::getType() const { return UI_TYPE_HTML_TABLE_FOOTER; } - bool UIHTMLTableFooter::isType( const Uint32& type ) const { return UIHTMLTableFooter::getType() == type || UIHTMLWidget::isType( type ); } diff --git a/src/eepp/ui/uihtmlwidget.cpp b/src/eepp/ui/uihtmlwidget.cpp index 2449f230e..a80deb935 100644 --- a/src/eepp/ui/uihtmlwidget.cpp +++ b/src/eepp/ui/uihtmlwidget.cpp @@ -93,14 +93,6 @@ UILayouter* UIHTMLWidget::getLayouter() { return mLayouter; } -Uint32 UIHTMLWidget::getType() const { - return UI_TYPE_HTML_WIDGET; -} - -bool UIHTMLWidget::isType( const Uint32& type ) const { - return UIHTMLWidget::getType() == type ? true : UILayout::isType( type ); -} - bool UIHTMLWidget::isPacking() const { UILayouter* layouter = const_cast( this )->getLayouter(); if ( layouter ) @@ -157,14 +149,6 @@ void UIHTMLWidget::setDisplay( CSSDisplay display ) { } } -bool UIHTMLWidget::isFlex() const { - return mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::InlineFlex; -} - -bool UIHTMLWidget::isGrid() const { - return mDisplay == CSSDisplay::Grid || mDisplay == CSSDisplay::InlineGrid; -} - Float UIHTMLWidget::getBaseline() const { if ( mLayouter ) { if ( isFlex() ) { @@ -1341,10 +1325,6 @@ void UIHTMLWidget::invalidateIntrinsicSize() { UIWidget::invalidateIntrinsicSize(); } -bool UIHTMLWidget::isOutOfFlow() const { - return mPosition == CSSPosition::Absolute || mPosition == CSSPosition::Fixed; -} - bool UIHTMLWidget::establishesBlockFormattingContext() const { if ( mFloat != CSSFloat::None || isOutOfFlow() || mDisplay == CSSDisplay::InlineBlock ) return true; diff --git a/src/eepp/ui/uilayout.cpp b/src/eepp/ui/uilayout.cpp index 863b9fc1c..9834020d4 100644 --- a/src/eepp/ui/uilayout.cpp +++ b/src/eepp/ui/uilayout.cpp @@ -51,14 +51,6 @@ void UILayout::onLayoutUpdate() { sendCommonEvent( Event::OnLayoutUpdate ); } -Uint32 UILayout::getType() const { - return UI_TYPE_LAYOUT; -} - -bool UILayout::isType( const Uint32& type ) const { - return UILayout::getType() == type ? true : UIWidget::isType( type ); -} - const Sizef& UILayout::getSize() const { if ( mDirtyLayout ) const_cast( this )->updateLayout(); diff --git a/src/eepp/ui/uilinearlayout.cpp b/src/eepp/ui/uilinearlayout.cpp index 2eb90a4fd..d2348bbf7 100644 --- a/src/eepp/ui/uilinearlayout.cpp +++ b/src/eepp/ui/uilinearlayout.cpp @@ -39,16 +39,16 @@ UILinearLayout::UILinearLayout( const std::string& tag, const UIOrientation& ori setClipType( ClipType::ContentBox ); } +UIOrientation UILinearLayout::getOrientation() const { + return mOrientation; +} + Uint32 UILinearLayout::getType() const { return UI_TYPE_LINEAR_LAYOUT; } bool UILinearLayout::isType( const Uint32& type ) const { - return UILinearLayout::getType() == type ? true : UILayout::isType( type ); -} - -UIOrientation UILinearLayout::getOrientation() const { - return mOrientation; + return UILinearLayout::getType() == type || UILayout::isType( type ); } UILinearLayout* UILinearLayout::setOrientation( const UIOrientation& orientation ) { diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index 156d601e1..997eb2e63 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -89,14 +89,6 @@ void UINode::nodeToWorldTranslation( Vector2f& Pos ) const { } } -Uint32 UINode::getType() const { - return UI_TYPE_UINODE; -} - -bool UINode::isType( const Uint32& type ) const { - return UINode::getType() == type || Node::isType( type ); -} - void UINode::setInternalPosition( const Vector2f& Pos ) { mDpPos = Pos; Transformable::setPosition( PixelDensity::dpToPx( Pos ) ); diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index ec2f06fca..244d59448 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -434,14 +434,6 @@ UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) { setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent ); } -Uint32 UIRichText::getType() const { - return UI_TYPE_RICHTEXT; -} - -bool UIRichText::isType( const Uint32& type ) const { - return UIRichText::getType() == type ? true : UIHTMLWidget::isType( type ); -} - const RichText& UIRichText::getRichText() { return mRichText; } diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index 5f6aafe37..9bf5de913 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -105,14 +105,6 @@ UIWidget::~UIWidget() { eeSAFE_DELETE( mTooltip ); } -Uint32 UIWidget::getType() const { - return UI_TYPE_WIDGET; -} - -bool UIWidget::isType( const Uint32& type ) const { - return UIWidget::getType() == type ? true : UINode::isType( type ); -} - void UIWidget::updateAnchorsDistances() { if ( NULL != mParentNode ) { mDistToBorder = Rect( mPosition.x, mPosition.y, diff --git a/src/tests/unit_tests/stringsoperations_tests.cpp b/src/tests/unit_tests/stringsoperations_tests.cpp index 1dcd7e310..67795dc73 100644 --- a/src/tests/unit_tests/stringsoperations_tests.cpp +++ b/src/tests/unit_tests/stringsoperations_tests.cpp @@ -18,6 +18,18 @@ UTEST( String, countLines ) { EXPECT_EQ( static_cast( 3 ), String::countLines( "\n\n" ) ); } +UTEST( String, fromStringView ) { + const std::string values = "1234.5px"; + Float floatValue = 0; + EXPECT_TRUE( String::fromString( floatValue, std::string_view( values ).substr( 0, 6 ) ) ); + EXPECT_NEAR( 1234.5f, floatValue, 0.0001f ); + EXPECT_FALSE( String::fromString( floatValue, std::string_view( values ) ) ); + + Int32 intValue = 0; + EXPECT_TRUE( String::fromString( intValue, std::string_view( values ).substr( 0, 4 ) ) ); + EXPECT_EQ( 1234, intValue ); +} + UTEST( FileSystem, fileCountLines ) { std::string path = Sys::getTempPath() + "eepp_test_count_lines.txt"; FileSystem::fileWrite( path, "A\nB\nC" ); @@ -153,7 +165,8 @@ UTEST( String, isLatin1 ) { EXPECT_TRUE( str255.isLatin1() ); // Complex string with Latin1 chars - String complexLatin1 = String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range + String complexLatin1 = + String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range // Note: 'ø' is 0xF8 (248), 'é' is 0xE9 (233). Both in Latin1. EXPECT_TRUE( complexLatin1.isLatin1() ); @@ -162,7 +175,6 @@ UTEST( String, isLatin1 ) { // 32 chars of 255 String longLatin1( 32, (String::StringBaseType)255 ); EXPECT_TRUE( longLatin1.isLatin1() ); - } } @@ -187,14 +199,15 @@ UTEST( String, isAsciiHighBit ) { UTEST( String, isAsciiPatterns ) { // Alternating String alt; - for (int i = 0; i < 100; i++) { - alt += (i % 2 == 0) ? 'a' : (char)128; + for ( int i = 0; i < 100; i++ ) { + alt += ( i % 2 == 0 ) ? 'a' : (char)128; } EXPECT_FALSE( alt.isAscii() ); // Block of invalid in middle of valid - String block(100, 'a'); - for(int i=40; i<60; i++) block[i] = 200; + String block( 100, 'a' ); + for ( int i = 40; i < 60; i++ ) + block[i] = 200; EXPECT_FALSE( block.isAscii() ); } diff --git a/src/tests/unit_tests/uicss_inheritance_tests.cpp b/src/tests/unit_tests/uicss_inheritance_tests.cpp index 6622f4be6..1bd5bd109 100644 --- a/src/tests/unit_tests/uicss_inheritance_tests.cpp +++ b/src/tests/unit_tests/uicss_inheritance_tests.cpp @@ -1349,6 +1349,33 @@ UTEST( CSSFunctions, ClampResolvesPixels ) { EXPECT_EQ( 50, resolved ); } +UTEST( CSSLength, ScalarParsing ) { + auto expectLength = [&]( const std::string& value, Float expectedValue, + StyleSheetLength::Unit expectedUnit ) { + const auto length = StyleSheetLength::fromString( value ); + EXPECT_NEAR( expectedValue, length.getValue(), 0.0001f ); + EXPECT_EQ( expectedUnit, length.getUnit() ); + }; + + expectLength( "12px", 12, StyleSheetLength::Unit::Px ); + expectLength( "-1.5em", -1.5f, StyleSheetLength::Unit::Em ); + expectLength( "+24dp", 24, StyleSheetLength::Unit::Dp ); + expectLength( ".75rem", 0.75f, StyleSheetLength::Unit::Rem ); + expectLength( "1e3px", 1000, StyleSheetLength::Unit::Px ); + expectLength( " 50% ", 50, StyleSheetLength::Unit::Percentage ); + expectLength( " center ", 50, StyleSheetLength::Unit::Percentage ); + expectLength( "left", 0, StyleSheetLength::Unit::Percentage ); + expectLength( "bottom", 100, StyleSheetLength::Unit::Percentage ); + expectLength( "10", 10, StyleSheetLength::Unit::Dp ); + expectLength( "10unknown", 10, StyleSheetLength::Unit::Dp ); + const auto invalid = StyleSheetLength::fromString( "invalid", 7 ); + EXPECT_EQ( 7, invalid.getValue() ); + EXPECT_EQ( StyleSheetLength::Unit::Px, invalid.getUnit() ); + + const auto pxAsDp = StyleSheetLength::fromString( "12px", 0, true ); + EXPECT_EQ( StyleSheetLength::Unit::Dp, pxAsDp.getUnit() ); +} + UTEST( CSSFunctions, ClampHonorsMinimum ) { auto len = StyleSheetLength::fromString( "clamp(40px, 30px, 100px)" ); Float resolved = len.asPixels( 0, Sizef::Zero, 96, 12, 12, nullptr );