diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 4486118bc..1a5edc961 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -377,7 +377,7 @@ "working_dir": "${project_root}/bin" }, { - "args": "-c system --hn-dark", + "args": "--hn-dark", "command": "${project_root}/bin/eepp-ui-html-debug", "name": "eepp-ui-html-debug", "working_dir": "${project_root}/bin" diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp index 30e926205..f85428bf2 100644 --- a/include/eepp/ui/css/propertydefinition.hpp +++ b/include/eepp/ui/css/propertydefinition.hpp @@ -212,6 +212,7 @@ enum class PropertyId : Uint32 { BorderBottomLeftRadius = String::hash( "border-bottom-left-radius" ), BorderBottomRightRadius = String::hash( "border-bottom-right-radius" ), BorderSmooth = String::hash( "border-smooth" ), + BoxSizing = String::hash( "box-sizing" ), BackgroundSmooth = String::hash( "background-smooth" ), ForegroundSmooth = String::hash( "foreground-smooth" ), TabBarHideOnSingleTab = String::hash( "tabbar-hide-on-single-tab" ), diff --git a/include/eepp/ui/csslayouttypes.hpp b/include/eepp/ui/csslayouttypes.hpp index 65ab80bc8..c57d38260 100644 --- a/include/eepp/ui/csslayouttypes.hpp +++ b/include/eepp/ui/csslayouttypes.hpp @@ -83,6 +83,14 @@ struct EE_API CSSClearHelper { static CSSClear fromString( std::string_view val ); }; +enum class CSSBoxSizing { ContentBox, BorderBox }; + +struct EE_API CSSBoxSizingHelper { + static std::string toString( CSSBoxSizing val ); + + static CSSBoxSizing fromString( std::string_view val ); +}; + enum class CSSFlexDirection { Row, RowReverse, Column, ColumnReverse }; struct EE_API CSSFlexDirectionHelper { diff --git a/include/eepp/ui/uihtmltable.hpp b/include/eepp/ui/uihtmltable.hpp index c36fc04b9..e64b3a97d 100644 --- a/include/eepp/ui/uihtmltable.hpp +++ b/include/eepp/ui/uihtmltable.hpp @@ -29,6 +29,8 @@ class EE_API UIHTMLTable : public UIHTMLWidget { virtual bool applyProperty( const StyleSheetProperty& attribute ); + Float cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const; + protected: virtual Uint32 onMessage( const NodeMessage* Msg ); diff --git a/include/eepp/ui/uihtmlwidget.hpp b/include/eepp/ui/uihtmlwidget.hpp index e07c7a351..d71bfba39 100644 --- a/include/eepp/ui/uihtmlwidget.hpp +++ b/include/eepp/ui/uihtmlwidget.hpp @@ -83,6 +83,10 @@ class EE_API UIHTMLWidget : public UILayout { void setCSSClear( CSSClear cssClear ); + CSSBoxSizing getBoxSizing() const { return mBoxSizing; } + + void setBoxSizing( CSSBoxSizing boxSizing ); + Rectf getNormalFlowLayoutPixelsMargin() const; const CSSBaselineAlignValue& getBaselineAlign() const { return mBaselineAlign; } @@ -276,6 +280,23 @@ class EE_API UIHTMLWidget : public UILayout { Float getBaseline() const; + Float getContainingBlockContentWidth() const; + + Float getContainingBlockContentHeight() const; + + Float lengthFromValueForCSS( const StyleSheetProperty& property, + const Float& defaultValue = 0 ) const; + + Float cssResolvedLengthToBorderBoxWidth( const Float& resolvedLength ) const; + + Float cssResolvedLengthToBorderBoxHeight( const Float& resolvedLength ) const; + + Float cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const; + + Float cssHeightPropertyToBorderBoxHeight( const StyleSheetProperty& property ) const; + + void updateCSSContentBoxFixedSize(); + virtual void onParentChange(); virtual void onPositionChange(); @@ -322,6 +343,7 @@ class EE_API UIHTMLWidget : public UILayout { CSSPosition mPosition{ CSSPosition::Static }; CSSFloat mFloat{ CSSFloat::None }; CSSClear mClear{ CSSClear::None }; + CSSBoxSizing mBoxSizing{ CSSBoxSizing::ContentBox }; CSSBaselineAlignValue mBaselineAlign; CSSVisibility mVisibility{ CSSVisibility::Visible }; std::string mTopEq{ "auto" }; diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 314d8db90..2cd199503 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -1388,6 +1388,44 @@ class EE_API UIWidget : public UINode { /**@return The property `height` converted as length */ Float getPropertyHeight() const; + /** + * @brief Converts an already resolved CSS width value into this widget's stored border-box + * width. + * + * The base widget has no CSS box model adjustment, so the value is returned as-is. HTML + * widgets override this to account for box-sizing and content offsets. + */ + virtual Float cssResolvedLengthToBorderBoxWidth( const Float& resolvedLength ) const; + + /** + * @brief Converts an already resolved CSS height value into this widget's stored border-box + * height. + * + * The base widget has no CSS box model adjustment, so the value is returned as-is. HTML + * widgets override this to account for box-sizing and content offsets. + */ + virtual Float cssResolvedLengthToBorderBoxHeight( const Float& resolvedLength ) const; + + /** + * @brief Resolves a CSS width property and converts it into this widget's stored border-box + * width. + * + * This is the sizing hook layout code should use when applying a CSS-specified width. Derived + * widgets can override the relative-size resolution or box conversion without layout-specific + * type checks. + */ + virtual Float cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const; + + /** + * @brief Resolves a CSS height property and converts it into this widget's stored border-box + * height. + * + * This is the sizing hook layout code should use when applying a CSS-specified height. Derived + * widgets can override the relative-size resolution or box conversion without layout-specific + * type checks. + */ + virtual Float cssHeightPropertyToBorderBoxHeight( const StyleSheetProperty& property ) const; + /* @return The width of the widget when size policy is match_parent */ Float getMatchParentWidth() const; diff --git a/src/eepp/graphics/csslayouttypes.cpp b/src/eepp/graphics/csslayouttypes.cpp index fbfb4408c..52d0bd327 100644 --- a/src/eepp/graphics/csslayouttypes.cpp +++ b/src/eepp/graphics/csslayouttypes.cpp @@ -102,6 +102,22 @@ CSSPosition CSSPositionHelper::fromString( std::string_view val ) { return position; } +std::string CSSBoxSizingHelper::toString( CSSBoxSizing val ) { + switch ( val ) { + case CSSBoxSizing::BorderBox: + return "border-box"; + case CSSBoxSizing::ContentBox: + default: + return "content-box"; + } +} + +CSSBoxSizing CSSBoxSizingHelper::fromString( std::string_view val ) { + if ( val == "border-box" ) + return CSSBoxSizing::BorderBox; + return CSSBoxSizing::ContentBox; +} + std::string CSSListStyleTypeHelper::toString( CSSListStyleType type ) { switch ( type ) { case CSSListStyleType::Disc: diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 3fd9fe173..27851a03f 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -108,15 +108,16 @@ void BlockLayouter::updateLayout() { const StyleSheetProperty* prop = nullptr; if ( mContainer->getLayoutWidthPolicy() == SizePolicy::Fixed && mContainer->getUIStyle() && ( prop = mContainer->getUIStyle()->getProperty( PropertyId::Width ) ) ) { - mContainer->setInternalPixelsSize( - { mContainer->lengthFromValue( *prop ), mContainer->getPixelsSize().getHeight() } ); + mContainer->setInternalPixelsSize( { mContainer->cssWidthPropertyToBorderBoxWidth( *prop ), + mContainer->getPixelsSize().getHeight() } ); } if ( !isTableCellInTableRow( mContainer ) && mContainer->getLayoutHeightPolicy() == SizePolicy::Fixed && mContainer->getUIStyle() && ( prop = mContainer->getUIStyle()->getProperty( PropertyId::Height ) ) ) { mContainer->setInternalPixelsSize( - { mContainer->getPixelsSize().getWidth(), mContainer->lengthFromValue( *prop ) } ); + { mContainer->getPixelsSize().getWidth(), + mContainer->cssHeightPropertyToBorderBoxHeight( *prop ) } ); } UIRichText::rebuildRichText( widget, *rt ); @@ -139,8 +140,8 @@ void BlockLayouter::updateLayout() { mContainer->getParent()->isWidget() && mContainer->getParent()->asType()->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { - totW = rt->getSize().getWidth() + mContainer->getPixelsContentOffset().Left + - mContainer->getPixelsContentOffset().Right; + const Rectf contentOffset = mContainer->getPixelsContentOffset(); + totW = rt->getSize().getWidth() + contentOffset.Left + contentOffset.Right; if ( !mContainer->getMaxWidthEq().empty() && totW > mContainer->getMaxSizePx().getWidth() ) mContainer->setClipType( ClipType::ContentBox ); @@ -167,8 +168,8 @@ void BlockLayouter::updateLayout() { mContainer->getParent()->isWidget() && mContainer->getParent()->asType()->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { - totH = rt->getSize().getHeight() + mContainer->getPixelsContentOffset().Top + - mContainer->getPixelsContentOffset().Bottom; + const Rectf contentOffset = mContainer->getPixelsContentOffset(); + totH = rt->getSize().getHeight() + contentOffset.Top + contentOffset.Bottom; if ( !mContainer->getMaxHeightEq().empty() && totH > mContainer->getMaxSizePx().getHeight() ) mContainer->setClipType( ClipType::ContentBox ); diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index 872a7516b..fce2af316 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -456,6 +456,7 @@ 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( "box-sizing", "content-box" ).setType( PropertyType::String ); registerProperty( "top", "auto" ) .setType( PropertyType::NumberLength ) .setRelativeTarget( PropertyRelativeTarget::ContainingBlockHeight ); diff --git a/src/eepp/ui/flexlayouter.cpp b/src/eepp/ui/flexlayouter.cpp index 21bdbd61f..381282612 100644 --- a/src/eepp/ui/flexlayouter.cpp +++ b/src/eepp/ui/flexlayouter.cpp @@ -189,8 +189,11 @@ void FlexLayouter::readItemStyle( UIWidget* child, FlexItem& item ) { // measureFlexItems against the flex container's inner main size. item.flexBasisValue = 0.f; } else { - item.flexBasisValue = mContainer->lengthFromValue( - val, CSS::PropertyRelativeTarget::ContainingBlockWidth, 0.f ); + Axis mainAxis = getMainAxis( mDirection ); + Float resolved = child->lengthFromValue( val, CSS::PropertyRelativeTarget::None, 0.f ); + item.flexBasisValue = mainAxis.horizontal + ? child->cssResolvedLengthToBorderBoxWidth( resolved ) + : child->cssResolvedLengthToBorderBoxHeight( resolved ); } } @@ -220,13 +223,13 @@ Float FlexLayouter::resolveFlexBasis( UIWidget* child, CSSFlexDirection, Float f child->getUIStyle() ) { const auto* wprop = child->getUIStyle()->getProperty( PropertyId::Width ); if ( wprop ) - return child->lengthFromValue( *wprop ); + return child->cssWidthPropertyToBorderBoxWidth( *wprop ); } if ( !mainAxis.horizontal && child->getLayoutHeightPolicy() == SizePolicy::Fixed && child->getUIStyle() ) { const auto* hprop = child->getUIStyle()->getProperty( PropertyId::Height ); if ( hprop ) - return child->lengthFromValue( *hprop ); + return child->cssHeightPropertyToBorderBoxHeight( *hprop ); } } @@ -383,7 +386,11 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis String::replaceAll( pctStr, "%", "" ); Float pct = 0.f; String::fromString( pct, pctStr ); - item.targetMainSize = containerInnerMain * pct / 100.f; + Float resolved = containerInnerMain * pct / 100.f; + item.targetMainSize = + mainAxis.horizontal + ? item.widget->cssResolvedLengthToBorderBoxWidth( resolved ) + : item.widget->cssResolvedLengthToBorderBoxHeight( resolved ); } } else { item.targetMainSize = @@ -397,13 +404,13 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis item.widget->getUIStyle() ) { const auto* wprop = item.widget->getUIStyle()->getProperty( PropertyId::Width ); if ( wprop ) - item.targetMainSize = item.widget->lengthFromValue( *wprop ); + item.targetMainSize = item.widget->cssWidthPropertyToBorderBoxWidth( *wprop ); } else if ( !mainAxis.horizontal && item.widget->getLayoutHeightPolicy() == SizePolicy::Fixed && item.widget->getUIStyle() ) { const auto* hprop = item.widget->getUIStyle()->getProperty( PropertyId::Height ); if ( hprop ) - item.targetMainSize = item.widget->lengthFromValue( *hprop ); + item.targetMainSize = item.widget->cssHeightPropertyToBorderBoxHeight( *hprop ); } } @@ -464,13 +471,13 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis if ( item.widget->getUIStyle() ) { const auto* minW = item.widget->getUIStyle()->getProperty( PropertyId::MinWidth ); if ( minW ) { - Float explicitMin = item.widget->lengthFromValue( *minW ); + Float explicitMin = item.widget->cssWidthPropertyToBorderBoxWidth( *minW ); if ( explicitMin > item.minMainSize ) item.minMainSize = explicitMin; } const auto* maxW = item.widget->getUIStyle()->getProperty( PropertyId::MaxWidth ); if ( maxW ) - item.maxMainSize = item.widget->lengthFromValue( *maxW ); + item.maxMainSize = item.widget->cssWidthPropertyToBorderBoxWidth( *maxW ); else item.maxMainSize = std::numeric_limits::max(); } @@ -505,13 +512,13 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis if ( item.widget->getUIStyle() ) { const auto* minH = item.widget->getUIStyle()->getProperty( PropertyId::MinHeight ); if ( minH ) { - Float explicitMin = item.widget->lengthFromValue( *minH ); + Float explicitMin = item.widget->cssHeightPropertyToBorderBoxHeight( *minH ); if ( explicitMin > item.minMainSize ) item.minMainSize = explicitMin; } const auto* maxH = item.widget->getUIStyle()->getProperty( PropertyId::MaxHeight ); if ( maxH ) - item.maxMainSize = item.widget->lengthFromValue( *maxH ); + item.maxMainSize = item.widget->cssHeightPropertyToBorderBoxHeight( *maxH ); else item.maxMainSize = std::numeric_limits::max(); } @@ -1241,7 +1248,7 @@ void FlexLayouter::updateLayout() { if ( widthPolicy == SizePolicy::Fixed && mContainer->getUIStyle() ) { const auto* wprop = mContainer->getUIStyle()->getProperty( PropertyId::Width ); if ( wprop ) { - Float rawWidth = mContainer->lengthFromValue( *wprop ); + Float rawWidth = widget->cssWidthPropertyToBorderBoxWidth( *wprop ); containerWidth = mContainer->fitMinMaxSizePx( Sizef( rawWidth, containerHeight ) ).getWidth(); } @@ -1250,7 +1257,7 @@ void FlexLayouter::updateLayout() { if ( heightPolicy == SizePolicy::Fixed && mContainer->getUIStyle() ) { const auto* hprop = mContainer->getUIStyle()->getProperty( PropertyId::Height ); if ( hprop ) { - Float rawHeight = mContainer->lengthFromValue( *hprop ); + Float rawHeight = widget->cssHeightPropertyToBorderBoxHeight( *hprop ); containerHeight = mContainer->fitMinMaxSizePx( Sizef( containerWidth, rawHeight ) ).getHeight(); } diff --git a/src/eepp/ui/gridlayouter.cpp b/src/eepp/ui/gridlayouter.cpp index e1a7da035..1aa134f4f 100644 --- a/src/eepp/ui/gridlayouter.cpp +++ b/src/eepp/ui/gridlayouter.cpp @@ -1060,7 +1060,8 @@ void GridLayouter::updateLayout() { } Float resolved = best * pct / 100.f; if ( resolved > 0.f ) { - mContainer->setInternalPixelsWidth( resolved ); + mContainer->setInternalPixelsWidth( + grid->cssResolvedLengthToBorderBoxWidth( resolved ) ); needResize = true; } } diff --git a/src/eepp/ui/tablelayouter.cpp b/src/eepp/ui/tablelayouter.cpp index d2b2158be..f0771ed3e 100644 --- a/src/eepp/ui/tablelayouter.cpp +++ b/src/eepp/ui/tablelayouter.cpp @@ -16,7 +16,7 @@ static Float specifiedHeightPx( UIWidget* widget ) { if ( prop == nullptr ) return 0.f; - return sanitizeFloat( widget->lengthFromValue( *prop ) ); + return sanitizeFloat( widget->cssHeightPropertyToBorderBoxHeight( *prop ) ); } static Float normalFlowChildrenBottomPx( UIWidget* widget ) { @@ -327,13 +327,15 @@ void TableLayouter::updateLayout() { if ( widget->getLayoutWidthPolicy() == SizePolicy::Fixed && widget->getUIStyle() && ( prop = widget->getUIStyle()->getProperty( PropertyId::Width ) ) ) { widget->asType()->setInternalPixelsSize( - { widget->lengthFromValue( *prop ), widget->getPixelsSize().getHeight() } ); + { widget->cssWidthPropertyToBorderBoxWidth( *prop ), + widget->getPixelsSize().getHeight() } ); } if ( widget->getLayoutHeightPolicy() == SizePolicy::Fixed && widget->getUIStyle() && ( prop = widget->getUIStyle()->getProperty( PropertyId::Height ) ) ) { widget->asType()->setInternalPixelsSize( - { widget->getPixelsSize().getWidth(), widget->lengthFromValue( *prop ) } ); + { widget->getPixelsSize().getWidth(), + widget->cssHeightPropertyToBorderBoxHeight( *prop ) } ); } computeIntrinsicWidths(); diff --git a/src/eepp/ui/uihtmltable.cpp b/src/eepp/ui/uihtmltable.cpp index 0ab2f5a33..319e45975 100644 --- a/src/eepp/ui/uihtmltable.cpp +++ b/src/eepp/ui/uihtmltable.cpp @@ -99,6 +99,10 @@ bool UIHTMLTable::applyProperty( const StyleSheetProperty& attribute ) { return UIHTMLWidget::applyProperty( attribute ); } +Float UIHTMLTable::cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const { + return lengthFromValueForCSS( property ); +} + void UIHTMLTable::computeIntrinsicWidths() const { UILayouter* layouter = const_cast( this )->getLayouter(); if ( layouter ) diff --git a/src/eepp/ui/uihtmlwidget.cpp b/src/eepp/ui/uihtmlwidget.cpp index f4abe2944..2449f230e 100644 --- a/src/eepp/ui/uihtmlwidget.cpp +++ b/src/eepp/ui/uihtmlwidget.cpp @@ -181,6 +181,115 @@ Float UIHTMLWidget::getBaseline() const { return 0.f; } +Float UIHTMLWidget::getContainingBlockContentWidth() const { + Node* parent = getParent(); + while ( parent && parent->isWidget() && parent->isType( UI_TYPE_HTML_WIDGET ) && + static_cast( parent )->isInline() ) + parent = parent->getParent(); + if ( !parent ) + return 0.f; + + Float width = parent->getPixelsSize().getWidth(); + if ( parent->isWidget() ) { + Rectf contentOffset = parent->asType()->getPixelsContentOffset(); + width -= contentOffset.Left + contentOffset.Right; + } + return eemax( 0.f, width ); +} + +Float UIHTMLWidget::getContainingBlockContentHeight() const { + Node* parent = getParent(); + while ( parent && parent->isWidget() && parent->isType( UI_TYPE_HTML_WIDGET ) && + static_cast( parent )->isInline() ) + parent = parent->getParent(); + if ( !parent ) + return 0.f; + + Float height = parent->getPixelsSize().getHeight(); + if ( parent->isWidget() ) { + Rectf contentOffset = parent->asType()->getPixelsContentOffset(); + height -= contentOffset.Top + contentOffset.Bottom; + } + return eemax( 0.f, height ); +} + +Float UIHTMLWidget::lengthFromValueForCSS( const StyleSheetProperty& property, + const Float& defaultValue ) const { + if ( property.getPropertyDefinition() ) { + switch ( property.getPropertyDefinition()->getRelativeTarget() ) { + case PropertyRelativeTarget::ContainingBlockWidth: + return convertLength( + StyleSheetLength::fromString( property.getValue(), defaultValue ), + getContainingBlockContentWidth() ); + case PropertyRelativeTarget::ContainingBlockHeight: + return convertLength( + StyleSheetLength::fromString( property.getValue(), defaultValue ), + getContainingBlockContentHeight() ); + default: + break; + } + } + return lengthFromValue( property, defaultValue ); +} + +Float UIHTMLWidget::cssResolvedLengthToBorderBoxWidth( const Float& resolvedLength ) const { + if ( mBoxSizing == CSSBoxSizing::BorderBox ) + return resolvedLength; + Rectf contentOffset = getPixelsContentOffset(); + return resolvedLength + contentOffset.Left + contentOffset.Right; +} + +Float UIHTMLWidget::cssResolvedLengthToBorderBoxHeight( const Float& resolvedLength ) const { + if ( mBoxSizing == CSSBoxSizing::BorderBox ) + return resolvedLength; + Rectf contentOffset = getPixelsContentOffset(); + return resolvedLength + contentOffset.Top + contentOffset.Bottom; +} + +Float UIHTMLWidget::cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const { + return cssResolvedLengthToBorderBoxWidth( lengthFromValueForCSS( property ) ); +} + +Float UIHTMLWidget::cssHeightPropertyToBorderBoxHeight( const StyleSheetProperty& property ) const { + return cssResolvedLengthToBorderBoxHeight( lengthFromValueForCSS( property ) ); +} + +void UIHTMLWidget::updateCSSContentBoxFixedSize() { + if ( getUIStyle() == nullptr ) + return; + + Sizef size( getPixelsSize() ); + bool changed = false; + + if ( getLayoutWidthPolicy() == SizePolicy::Fixed ) { + const auto* width = getUIStyle()->getProperty( PropertyId::Width ); + if ( width && width->value() != "auto" ) { + size.setWidth( cssWidthPropertyToBorderBoxWidth( *width ) ); + changed = true; + } + } + + if ( getLayoutHeightPolicy() == SizePolicy::Fixed ) { + const auto* height = getUIStyle()->getProperty( PropertyId::Height ); + if ( height && height->value() != "auto" ) { + size.setHeight( cssHeightPropertyToBorderBoxHeight( *height ) ); + changed = true; + } + } + + if ( changed ) + setPixelsSize( size ); +} + +void UIHTMLWidget::setBoxSizing( CSSBoxSizing boxSizing ) { + if ( mBoxSizing != boxSizing ) { + mBoxSizing = boxSizing; + updateCSSContentBoxFixedSize(); + notifyLayoutAttrChange( LayoutInvalidation::Self ); + notifyLayoutAttrChangeParent( LayoutInvalidation::ParentChildChange ); + } +} + void UIHTMLWidget::setVisibility( CSSVisibility val ) { if ( mVisibility != val ) { mVisibility = val; @@ -597,6 +706,7 @@ void UIHTMLWidget::setJustifySelf( CSSJustifySelf val ) { std::vector UIHTMLWidget::getPropertiesImplemented() const { auto props = UILayout::getPropertiesImplemented(); auto local = { PropertyId::Display, + PropertyId::BoxSizing, PropertyId::Position, PropertyId::Float, PropertyId::Clear, @@ -650,6 +760,8 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD switch ( propertyDef->getPropertyId() ) { case PropertyId::Display: return CSSDisplayHelper::toString( mDisplay ); + case PropertyId::BoxSizing: + return CSSBoxSizingHelper::toString( mBoxSizing ); case PropertyId::Position: return CSSPositionHelper::toString( mPosition ); case PropertyId::Float: @@ -743,6 +855,10 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) { setDisplay( CSSDisplayHelper::fromString( attribute.asString() ) ); return true; } + case PropertyId::BoxSizing: { + setBoxSizing( CSSBoxSizingHelper::fromString( attribute.asString() ) ); + return true; + } case PropertyId::Position: { setCSSPosition( CSSPositionHelper::fromString( attribute.asString() ) ); return true; @@ -765,6 +881,20 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) { mOverflowCreatesBlockFormattingContext = val != "visible"; return UILayout::applyProperty( attribute ); } + case PropertyId::Width: + case PropertyId::Height: + case PropertyId::PaddingLeft: + case PropertyId::PaddingRight: + case PropertyId::PaddingTop: + case PropertyId::PaddingBottom: + case PropertyId::BorderLeftWidth: + case PropertyId::BorderRightWidth: + case PropertyId::BorderTopWidth: + case PropertyId::BorderBottomWidth: { + bool applied = UILayout::applyProperty( attribute ); + updateCSSContentBoxFixedSize(); + return applied; + } case PropertyId::ZIndex: { setZIndex( attribute.asInt() ); return true; diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index cc960769d..c0f04c8dd 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -2051,8 +2051,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri const StyleSheetProperty* wprop = widget->getUIStyle()->getProperty( PropertyId::Width ); if ( wprop && StyleSheetLength::isPercentage( wprop->value() ) ) { - widget->setPixelsSize( { widget->lengthFromValue( *wprop ), - widget->getPixelsSize().getHeight() } ); + Float width = widget->cssWidthPropertyToBorderBoxWidth( *wprop ); + widget->setPixelsSize( { width, widget->getPixelsSize().getHeight() } ); } } if ( widget->getLayoutHeightPolicy() == SizePolicy::Fixed && @@ -2060,8 +2060,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri const StyleSheetProperty* hprop = widget->getUIStyle()->getProperty( PropertyId::Height ); if ( hprop && StyleSheetLength::isPercentage( hprop->value() ) ) { - widget->setPixelsSize( { widget->getPixelsSize().getWidth(), - widget->lengthFromValue( *hprop ) } ); + Float height = widget->cssHeightPropertyToBorderBoxHeight( *hprop ); + widget->setPixelsSize( { widget->getPixelsSize().getWidth(), height } ); } } } diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index 5b3fd62ef..129f93719 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -2907,6 +2907,22 @@ Float UIWidget::getPropertyHeight() const { return 0.f; } +Float UIWidget::cssResolvedLengthToBorderBoxWidth( const Float& resolvedLength ) const { + return resolvedLength; +} + +Float UIWidget::cssResolvedLengthToBorderBoxHeight( const Float& resolvedLength ) const { + return resolvedLength; +} + +Float UIWidget::cssWidthPropertyToBorderBoxWidth( const StyleSheetProperty& property ) const { + return lengthFromValue( property ); +} + +Float UIWidget::cssHeightPropertyToBorderBoxHeight( const StyleSheetProperty& property ) const { + return lengthFromValue( property ); +} + void UIWidget::setStyleSheetProperties( const CSS::StyleSheetProperties& properties ) { mStyle->setStyleSheetProperties( properties ); for ( const auto& [_, property] : properties ) diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 8d5cce3a2..cc51a36cf 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -2779,6 +2779,171 @@ UTEST( UIHTML, InlineBlockExplicitWidth ) { Engine::destroySingleton(); } +UTEST( UIHTML, FixedBlockWidthUsesContentBoxWithPadding ) { + Engine::instance()->createWindow( WindowSettings( 1024, 653, "Fixed Block Content Box Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + + UISceneNode* sceneNode = init_test_inline_block(); + + const std::string html = R"html( + + + + + + +
+
+
+
+
+
+ + +)html"; + + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + sceneNode->update( Seconds( 1 ) ); + sceneNode->updateDirtyLayouts(); + + auto* wrapper = sceneNode->getRoot()->find( "wrapper" )->asType(); + auto* child = sceneNode->getRoot()->find( "child" )->asType(); + auto* borderWrapper = sceneNode->getRoot()->find( "borderWrapper" )->asType(); + auto* borderChild = sceneNode->getRoot()->find( "borderChild" )->asType(); + + ASSERT_TRUE( wrapper != nullptr ); + ASSERT_TRUE( child != nullptr ); + ASSERT_TRUE( borderWrapper != nullptr ); + ASSERT_TRUE( borderChild != nullptr ); + + EXPECT_NEAR( wrapper->getPixelsSize().getWidth(), 340.f, 1.f ); + EXPECT_NEAR( wrapper->getPixelsSize().getWidth() - wrapper->getPixelsContentOffset().Left - + wrapper->getPixelsContentOffset().Right, + 300.f, 1.f ); + EXPECT_NEAR( child->getPixelsPosition().x, 20.f, 1.f ); + EXPECT_NEAR( child->getPixelsSize().getWidth(), 300.f, 1.f ); + + EXPECT_NEAR( borderWrapper->getPixelsSize().getWidth(), 300.f, 1.f ); + EXPECT_NEAR( borderWrapper->getPixelsSize().getWidth() - + borderWrapper->getPixelsContentOffset().Left - + borderWrapper->getPixelsContentOffset().Right, + 260.f, 1.f ); + EXPECT_NEAR( borderChild->getPixelsPosition().x, 20.f, 1.f ); + EXPECT_NEAR( borderChild->getPixelsSize().getWidth(), 260.f, 1.f ); + + Engine::destroySingleton(); +} + +UTEST( UIHTML, BoxSizingAppliesToFlexBasis ) { + Engine::instance()->createWindow( WindowSettings( 1024, 653, "Flex Box Sizing Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + + UISceneNode* sceneNode = init_test_inline_block(); + + const std::string html = R"html( + + + + + + +
+
+
+
+ + +)html"; + + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + sceneNode->update( Seconds( 1 ) ); + sceneNode->updateDirtyLayouts(); + + auto* contentItem = sceneNode->getRoot()->find( "contentItem" )->asType(); + auto* borderItem = sceneNode->getRoot()->find( "borderItem" )->asType(); + + ASSERT_TRUE( contentItem != nullptr ); + ASSERT_TRUE( borderItem != nullptr ); + + EXPECT_NEAR( contentItem->getPixelsSize().getWidth(), 340.f, 1.f ); + EXPECT_NEAR( borderItem->getPixelsPosition().x, 340.f, 1.f ); + EXPECT_NEAR( borderItem->getPixelsSize().getWidth(), 300.f, 1.f ); + + Engine::destroySingleton(); +} + +UTEST( UIHTML, BoxSizingAppliesToGridAndTableContainers ) { + Engine::instance()->createWindow( WindowSettings( 1024, 653, "Grid Table Box Sizing Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + + UISceneNode* sceneNode = init_test_inline_block(); + + const std::string html = R"html( + + + + + + +
+
+
x
+
x
+ + +)html"; + + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + sceneNode->update( Seconds( 1 ) ); + sceneNode->updateDirtyLayouts(); + + auto* gridContent = sceneNode->getRoot()->find( "gridContent" )->asType(); + auto* gridBorder = sceneNode->getRoot()->find( "gridBorder" )->asType(); + auto* tableContent = sceneNode->getRoot()->find( "tableContent" )->asType(); + auto* tableBorder = sceneNode->getRoot()->find( "tableBorder" )->asType(); + + ASSERT_TRUE( gridContent != nullptr ); + ASSERT_TRUE( gridBorder != nullptr ); + ASSERT_TRUE( tableContent != nullptr ); + ASSERT_TRUE( tableBorder != nullptr ); + + EXPECT_NEAR( gridContent->getPixelsSize().getWidth(), 340.f, 1.f ); + EXPECT_NEAR( gridBorder->getPixelsSize().getWidth(), 300.f, 1.f ); + EXPECT_NEAR( tableContent->getPixelsSize().getWidth(), 300.f, 1.f ); + EXPECT_NEAR( tableContent->getPixelsSize().getWidth() - + tableContent->getPixelsContentOffset().Left - + tableContent->getPixelsContentOffset().Right, + 260.f, 1.f ); + EXPECT_NEAR( tableBorder->getPixelsSize().getWidth(), 300.f, 1.f ); + EXPECT_NEAR( tableBorder->getPixelsSize().getWidth() - + tableBorder->getPixelsContentOffset().Left - + tableBorder->getPixelsContentOffset().Right, + 260.f, 1.f ); + + Engine::destroySingleton(); +} + UTEST( UIHTML, InlineBlockMixedContent ) { Engine::instance()->createWindow( WindowSettings( 1024, 653, "Inline Block Mixed Content Test", WindowStyle::Default, WindowBackend::Default,