فهرست منبع

Implement the 'box-sizing' property.

Michael Ragazzon 5 سال پیش
والد
کامیت
d5615eda9d

+ 4 - 0
Include/RmlUi/Core/ComputedValues.h

@@ -71,6 +71,8 @@ using Left = LengthPercentageAuto;
 enum class Float : uint8_t { None, Left, Right };
 enum class Clear : uint8_t { None, Left, Right, Both };
 
+enum class BoxSizing : uint8_t { ContentBox, BorderBox };
+
 using ZIndex = NumberAuto;
 
 using Width = LengthPercentageAuto;
@@ -152,6 +154,8 @@ struct ComputedValues
 	Float float_ = Float::None;
 	Clear clear = Clear::None;
 
+	BoxSizing box_sizing = BoxSizing::ContentBox;
+
 	ZIndex z_index = { ZIndex::Auto };
 
 	Width width = { Width::Auto };

+ 1 - 0
Include/RmlUi/Core/ID.h

@@ -104,6 +104,7 @@ enum class PropertyId : uint8_t
 	Left,
 	Float,
 	Clear,
+	BoxSizing,
 	ZIndex,
 	Width,
 	MinWidth,

+ 3 - 0
Source/Core/ElementStyle.cpp

@@ -711,6 +711,9 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 		case PropertyId::Clear:
 			values.clear = (Clear)p->Get<int>();
 			break;
+		case PropertyId::BoxSizing:
+			values.box_sizing = (BoxSizing)p->Get<int>();
+			break;
 
 		case PropertyId::ZIndex:
 			values.z_index = (p->unit == Property::KEYWORD ? ZIndex(ZIndex::Auto) : ZIndex(ZIndex::Number, p->Get<float>()));

+ 2 - 4
Source/Core/IdNameMap.h

@@ -60,12 +60,10 @@ public:
 		(void)inserted;
 	}
 
-	void AssertAllInserted(ID number_of_defined_ids) const
+	bool AssertAllInserted(ID number_of_defined_ids) const
 	{
 		std::ptrdiff_t cnt = std::count_if(name_map.begin(), name_map.end(), [](const String& name) { return !name.empty(); });
-		RMLUI_ASSERT(cnt == (std::ptrdiff_t)number_of_defined_ids && reverse_map.size() == (size_t)number_of_defined_ids);
-		(void)number_of_defined_ids;
-		(void)cnt;
+		return cnt == (std::ptrdiff_t)number_of_defined_ids && reverse_map.size() == (size_t)number_of_defined_ids;
 	}
 
 	ID GetId(const String& name) const

+ 1 - 1
Source/Core/LayoutBlockBox.cpp

@@ -611,7 +611,7 @@ float LayoutBlockBox::GetShrinkToFitWidth() const
 				content_width = Math::Max(content_width, width_value);
 			}
 
-			content_width = LayoutDetails::ClampWidth(content_width, computed, block_width);
+			content_width = LayoutDetails::ClampWidth(content_width, computed, box, block_width);
 		}
 		else
 		{

+ 52 - 40
Source/Core/LayoutDetails.cpp

@@ -36,10 +36,22 @@
 
 namespace Rml {
 
+static inline float BorderWidthToContentWidth(float border_width, const Box& box)
+{
+	const float border_padding_edges_width = box.GetEdge(Box::BORDER, Box::LEFT) + box.GetEdge(Box::BORDER, Box::RIGHT) + box.GetEdge(Box::PADDING, Box::LEFT) + box.GetEdge(Box::PADDING, Box::RIGHT);
+	return Math::Max(0.0f, border_width - border_padding_edges_width);
+}
+static inline float BorderHeightToContentHeight(float border_height, const Box& box)
+{
+	const float border_padding_edges_height = box.GetEdge(Box::BORDER, Box::TOP) + box.GetEdge(Box::BORDER, Box::BOTTOM) + box.GetEdge(Box::PADDING, Box::TOP) + box.GetEdge(Box::PADDING, Box::BOTTOM);
+	return Math::Max(0.0f, border_height - border_padding_edges_height);
+}
+
+
 // Generates the box for an element.
 void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element, float override_shrink_to_fit_width)
 {
-	if (element == nullptr)
+	if (!element)
 	{
 		box.SetContent(containing_block);
 		return;
@@ -48,14 +60,10 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 	const ComputedValues& computed = element->GetComputedValues();
 
 	// Calculate the padding area.
-	float padding = ResolveValue(computed.padding_top, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::TOP, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_right, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::RIGHT, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_bottom, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::BOTTOM, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_left, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::LEFT, Math::Max(0.0f, padding));
+	box.SetEdge(Box::PADDING, Box::TOP, Math::Max(0.0f, ResolveValue(computed.padding_top, containing_block.x)));
+	box.SetEdge(Box::PADDING, Box::RIGHT, Math::Max(0.0f, ResolveValue(computed.padding_right, containing_block.x)));
+	box.SetEdge(Box::PADDING, Box::BOTTOM, Math::Max(0.0f, ResolveValue(computed.padding_bottom, containing_block.x)));
+	box.SetEdge(Box::PADDING, Box::LEFT, Math::Max(0.0f, ResolveValue(computed.padding_left, containing_block.x)));
 
 	// Calculate the border area.
 	box.SetEdge(Box::BORDER, Box::TOP, Math::Max(0.0f, computed.border_top_width));
@@ -115,8 +123,8 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 	{
 		if (replaced_element)
 		{
-			content_area.x = ClampWidth(content_area.x, computed, containing_block.x);
-			content_area.y = ClampHeight(content_area.y, computed, containing_block.y);
+			content_area.x = ClampWidth(content_area.x, computed, box, containing_block.x);
+			content_area.y = ClampHeight(content_area.y, computed, box, containing_block.y);
 		}
 
 		// If the element was not replaced, then we leave its dimension as unsized (-1, -1) and ignore the width and
@@ -152,6 +160,12 @@ void LayoutDetails::BuildBox(Box& box, float& min_height, float& max_height, Lay
 		auto& computed = element->GetComputedValues();
 		min_height = ResolveValue(computed.min_height, containing_block.y);
 		max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block.y));
+
+		if (computed.box_sizing == Style::BoxSizing::BorderBox)
+		{
+			min_height = BorderHeightToContentHeight(min_height, box);
+			max_height = BorderHeightToContentHeight(max_height, box);
+		}
 	}
 	else
 	{
@@ -161,20 +175,32 @@ void LayoutDetails::BuildBox(Box& box, float& min_height, float& max_height, Lay
 }
 
 // Clamps the width of an element based from its min-width and max-width properties.
-float LayoutDetails::ClampWidth(float width, const ComputedValues& computed, float containing_block_width)
+float LayoutDetails::ClampWidth(float width, const ComputedValues& computed, const Box& box, float containing_block_width)
 {
 	float min_width = ResolveValue(computed.min_width, containing_block_width);
 	float max_width = (computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block_width));
 
+	if (computed.box_sizing == Style::BoxSizing::BorderBox)
+	{
+		min_width = BorderWidthToContentWidth(min_width, box);
+		max_width = BorderWidthToContentWidth(max_width, box);
+	}
+
 	return Math::Clamp(width, min_width, max_width);
 }
 
 // Clamps the height of an element based from its min-height and max-height properties.
-float LayoutDetails::ClampHeight(float height, const ComputedValues& computed, float containing_block_height)
+float LayoutDetails::ClampHeight(float height, const ComputedValues& computed, const Box& box, float containing_block_height)
 {
 	float min_height = ResolveValue(computed.min_height, containing_block_height);
 	float max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block_height));
 
+	if (computed.box_sizing == Style::BoxSizing::BorderBox)
+	{
+		min_height = BorderHeightToContentHeight(min_height, box);
+		max_height = BorderHeightToContentHeight(max_height, box);
+	}
+
 	return Math::Clamp(height, min_height, max_height);
 }
 
@@ -242,22 +268,15 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vect
 	Vector2f content_area = box.GetSize();
 
 	// Determine if the element has an automatic width, and if not calculate it.
-	bool width_auto;
-	if (content_area.x >= 0)
-	{
-		width_auto = false;
-	}
-	else
+	bool width_auto = false;
+	if (content_area.x < 0)
 	{
 		if (computed.width.type == Style::Width::Auto)
-		{
 			width_auto = true;
-		}
-		else
-		{
-			width_auto = false;
+		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
 			content_area.x = ResolveValue(computed.width, containing_block.x);
-		}
+		else
+			content_area.x = BorderWidthToContentWidth(ResolveValue(computed.width, containing_block.x), box);
 	}
 
 	// Determine if the element has automatic margins.
@@ -342,7 +361,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vect
 
 	// Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if
 	// they were set to auto.
-	float clamped_width = ClampWidth(content_area.x, computed, containing_block.x);
+	float clamped_width = ClampWidth(content_area.x, computed, box, containing_block.x);
 	if (clamped_width != content_area.x)
 	{
 		content_area.x = clamped_width;
@@ -371,22 +390,15 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 	Vector2f content_area = box.GetSize();
 
 	// Determine if the element has an automatic height, and if not calculate it.
-	bool height_auto;
-	if (content_area.y >= 0)
+	bool height_auto = false;
+	if (content_area.y < 0)
 	{
-		height_auto = false;
-	}
-	else
-	{
-		if (computed.height.type == Style::Height::Auto)
-		{
+		if (computed.height.type == Style::Width::Auto)
 			height_auto = true;
-		}
-		else
-		{
-			height_auto = false;
+		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
 			content_area.y = ResolveValue(computed.height, containing_block_height);
-		}
+		else
+			content_area.y = BorderHeightToContentHeight(ResolveValue(computed.height, containing_block_height), box);
 	}
 
 	// Determine if the element has automatic margins.
@@ -463,7 +475,7 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 	{
 		// Clamp the calculated height; if the height is changed by the clamp, then the margins need to be recalculated if
 		// they were set to auto.
-		float clamped_height = ClampHeight(content_area.y, computed, containing_block_height);
+		float clamped_height = ClampHeight(content_area.y, computed, box, containing_block_height);
 		if (clamped_height != content_area.y)
 		{
 			content_area.y = clamped_height;

+ 2 - 2
Source/Core/LayoutDetails.h

@@ -66,13 +66,13 @@ public:
 	/// @param[in] element The element to read the properties from.
 	/// @param[in] containing_block_width The width of the element's containing block.
 	/// @return The clamped width.
-	static float ClampWidth(float width, const ComputedValues& computed, float containing_block_width);
+	static float ClampWidth(float width, const ComputedValues& computed, const Box& box, float containing_block_width);
 	/// Clamps the height of an element based from its min-height and max-height properties.
 	/// @param[in] height The height to clamp.
 	/// @param[in] element The element to read the properties from.
 	/// @param[in] containing_block_height The height of the element's containing block.
 	/// @return The clamped height.
-	static float ClampHeight(float height, const ComputedValues& computed, float containing_block_height);
+	static float ClampHeight(float height, const ComputedValues& computed, const Box& box, float containing_block_height);
 
 	/// Returns the fully-resolved, fixed-width and -height containing block from a block box.
 	/// @param[in] containing_box The leaf box.

+ 4 - 2
Source/Core/StyleSheetSpecification.cpp

@@ -326,6 +326,8 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::Float, "float", "none", false, true).AddParser("keyword", "none, left, right");
 	RegisterProperty(PropertyId::Clear, "clear", "none", false, true).AddParser("keyword", "none, left, right, both");
 
+	RegisterProperty(PropertyId::BoxSizing, "box-sizing", "content-box", false, true).AddParser("keyword", "content-box, border-box");
+
 	RegisterProperty(PropertyId::ZIndex, "z-index", "auto", false, false)
 		.AddParser("keyword", "auto")
 		.AddParser("number");
@@ -403,8 +405,8 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	// Rare properties (not added to computed values)
 	RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string");
 
-	instance->properties.property_map->AssertAllInserted(PropertyId::NumDefinedIds);
-	instance->properties.shorthand_map->AssertAllInserted(ShorthandId::NumDefinedIds);
+	RMLUI_ASSERTMSG(instance->properties.property_map->AssertAllInserted(PropertyId::NumDefinedIds), "Missing specification for one or more Property IDs.");
+	RMLUI_ASSERTMSG(instance->properties.shorthand_map->AssertAllInserted(ShorthandId::NumDefinedIds), "Missing specification for one or more Shorthand IDs.");
 }
 
 } // namespace Rml

+ 67 - 0
Tests/Data/VisualTests/box_sizing.rml

@@ -0,0 +1,67 @@
+<rml>
+<head>
+    <title>Box-sizing property</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css-ui-3/#box-sizing" />
+	<meta name="Description" content="Box-sizing property." />
+	<style>
+		div > div {
+			width: 50%;
+			margin: 10px 0;
+			padding: 20px;
+			border: 10px #999;
+			background-color: #aaf;
+		}
+		.border-box {
+			background-color: #aea;
+			box-sizing: border-box;
+		}
+		.float {
+			float: left;
+		}
+		.margin {
+			width: 23.3%;  /* width + margins should be 33.3% */
+			margin-left: 5%;
+			margin-right: 5%;
+			border-width: 20px;
+		}
+		.fill {
+			height: 200px;
+			border: 15px #ffa;
+			background: #f00;
+			overflow: auto;
+		}
+		.fill > div {
+			width: 100%;
+			height: 50%;
+			margin: 0 auto;
+		}
+	</style>
+</head>
+
+<body>
+<p>Setting the 'box-sizing' property on elements using the same width and height.</p>
+<div>
+	<div>box-sizing: content-box</div>
+	<div class="border-box">box-sizing: border-box</div>
+</div>
+<hr/>
+<p>The following boxes should line up side-by-side.</p>
+<div>
+	<div class="border-box float"/>
+	<div class="border-box float"/>
+</div>
+<hr/>
+<div>
+	<div class="margin border-box float"/>
+	<div class="margin border-box float"/>
+	<div class="margin border-box float"/>
+</div>
+<hr/>
+<p>There should be no scrollbars, and no red visible.</p>
+<div class="fill">
+	<div class="border-box"/>
+	<div class="border-box"/>
+</div>
+</body>
+</rml>