Browse Source

Margins of absolutely positioned elements now correctly consider inset (top/right/bottom/left) properties

- Auto margins now subtract any inset height or width.
- This allows inset properties to better control the position when using auto-margins.
Michael Ragazzon 2 years ago
parent
commit
1c20acac18
1 changed files with 32 additions and 39 deletions
  1. 32 39
      Source/Core/Layout/LayoutDetails.cpp

+ 32 - 39
Source/Core/Layout/LayoutDetails.cpp

@@ -428,35 +428,30 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		}
 	}
 
+	const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed);
+	const bool inset_auto = (computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto);
 	const bool width_auto = (content_area.x < 0);
 
-	// If the width is set to auto, we need to calculate the width
+	auto GetInsetWidth = [&] {
+		// For absolutely positioned elements (and only those), the 'left' and 'right' values are part of the box's width constraint.
+		if (absolutely_positioned)
+			return ResolveValue(computed.left(), containing_block.x) + ResolveValue(computed.right(), containing_block.x);
+		return 0.f;
+	};
+
+	// If the width is set to auto, we need to calculate the width.
 	if (width_auto)
 	{
 		// Apply the shrink-to-fit algorithm here to find the width of the element.
 		// See CSS 2.1 section 10.3.7 for when this should be applied.
 		const bool shrink_to_fit = !replaced_element &&
-			((computed.float_() != Style::Float::None) ||
-				((computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed) &&
-					(computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto)) ||
+			((computed.float_() != Style::Float::None) || (absolutely_positioned && inset_auto) ||
 				(computed.display() == Style::Display::InlineBlock));
 
 		if (!shrink_to_fit)
 		{
-			float left = 0.0f, right = 0.0f;
-			// If we are dealing with an absolutely positioned element we need to
-			// consider if the left and right properties are set, since the width can be affected.
-			if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
-			{
-				if (computed.left().type != Style::Left::Auto)
-					left = ResolveValue(computed.left(), containing_block.x);
-				if (computed.right().type != Style::Right::Auto)
-					right = ResolveValue(computed.right(), containing_block.x);
-			}
-
-			// We resolve any auto margins to 0 and the width is set to whatever is left of the containing block.
-			content_area.x = containing_block.x -
-				(left + box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) + right);
+			// The width is set to whatever remains of the containing block.
+			content_area.x = containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING));
 			content_area.x = Math::Max(0.0f, content_area.x);
 		}
 		else if (override_shrink_to_fit_width >= 0)
@@ -472,7 +467,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	else if (num_auto_margins > 0)
 	{
-		const float margin = (containing_block.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN)) / float(num_auto_margins);
+		const float margin = (containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN))) / float(num_auto_margins);
 
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
@@ -522,38 +517,36 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 		}
 	}
 
+	const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed);
+	const bool inset_auto = (computed.top().type == Style::Top::Auto || computed.bottom().type == Style::Bottom::Auto);
 	const bool height_auto = (content_area.y < 0);
 
-	// If the height is set to auto, we need to calculate the height
+	auto GetInsetHeight = [&] {
+		// For absolutely positioned elements (and only those), the 'top' and 'bottom' values are part of the box's height constraint.
+		if (absolutely_positioned)
+			return ResolveValue(computed.top(), containing_block_height) + ResolveValue(computed.bottom(), containing_block_height);
+		return 0.f;
+	};
+
+	// If the height is set to auto, we need to calculate the height.
 	if (height_auto)
 	{
 		// If the height is set to auto for a box in normal flow, the height is set to -1.
 		content_area.y = -1;
 
-		// But if we are dealing with an absolutely positioned element we need to
-		// consider if the top and bottom properties are set, since the height can be affected.
-		if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
+		// But if we are dealing with an absolutely positioned element we need to consider if the top and bottom
+		// properties are set, since the height can be affected.
+		if (absolutely_positioned && !inset_auto)
 		{
-			float top = 0.0f, bottom = 0.0f;
-
-			if (computed.top().type != Style::Top::Auto && computed.bottom().type != Style::Bottom::Auto)
-			{
-				top = ResolveValue(computed.top(), containing_block_height);
-				bottom = ResolveValue(computed.bottom(), containing_block_height);
-
-				// The height gets resolved to whatever is left of the containing block
-				content_area.y = containing_block_height -
-					(top + box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) + bottom);
-				content_area.y = Math::Max(0.0f, content_area.y);
-			}
+			// The height is set to whatever remains of the containing block.
+			content_area.y = containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING));
+			content_area.y = Math::Max(0.0f, content_area.y);
 		}
 	}
-	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
+	// Otherwise, the margins that are set to auto will pick up the remaining height of the containing block.
 	else if (num_auto_margins > 0)
 	{
-		float margin = 0;
-		if (content_area.y >= 0)
-			margin = (containing_block_height - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN)) / num_auto_margins;
+		const float margin = (containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN))) / float(num_auto_margins);
 
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::TOP, margin);