فهرست منبع

Move ShrinkToFit calculation into FormatIndependent, call into each formatting context as needed

BuildBox will no longer do any formatting. Shrink-to-fit width must be determined manually by the caller.
Michael Ragazzon 5 ماه پیش
والد
کامیت
f02f3dc8ce

+ 11 - 0
Source/Core/Layout/BlockFormattingContext.cpp

@@ -153,6 +153,17 @@ UniquePtr<LayoutBox> BlockFormattingContext::Format(ContainerBox* parent_contain
 	return container;
 }
 
+float BlockFormattingContext::DetermineMaxContentWidth(Element* element, const Box& initial_box, const FormattingMode& formatting_mode)
+{
+	RMLUI_ASSERT(formatting_mode.constraint == FormattingMode::Constraint::MaxContent);
+	const Vector2f containing_block(-1.f);
+	RootBox root(Box(containing_block), formatting_mode);
+
+	UniquePtr<LayoutBox> layout_box = BlockFormattingContext::Format(&root, element, containing_block, initial_box);
+
+	return layout_box->GetShrinkToFitWidth();
+}
+
 bool BlockFormattingContext::FormatBlockBox(BlockContainer* parent_container, Element* element)
 {
 	RMLUI_ZoneScopedC(0x2F4F4F);

+ 3 - 0
Source/Core/Layout/BlockFormattingContext.h

@@ -38,6 +38,7 @@ class Box;
 class BlockContainer;
 class ContainerBox;
 class LayoutBox;
+struct FormattingMode;
 
 /*
     Places boxes according to normal flow, while handling floated boxes.
@@ -53,6 +54,8 @@ class BlockFormattingContext final : public FormattingContext {
 public:
 	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, Vector2f containing_block, const Box& box);
 
+	static float DetermineMaxContentWidth(Element* element, const Box& initial_box, const FormattingMode& formatting_mode);
+
 private:
 	// Format the element as a block box, including its children.
 	// @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted.

+ 27 - 18
Source/Core/Layout/FlexFormattingContext.cpp

@@ -42,18 +42,37 @@ namespace Rml {
 
 UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_container, Element* element, Vector2f containing_block,
 	const Box& initial_box)
+{
+	const ComputedValues& computed = element->GetComputedValues();
+
+	Vector2f flex_min_size, flex_max_size;
+	LayoutDetails::GetMinMaxWidth(flex_min_size.x, flex_max_size.x, computed, initial_box, containing_block.x);
+	LayoutDetails::GetMinMaxHeight(flex_min_size.y, flex_max_size.y, computed, initial_box, containing_block.y);
+
+	return FlexFormattingContext::FormatImpl(parent_container, element, initial_box, flex_min_size, flex_max_size);
+}
+
+float FlexFormattingContext::DetermineMaxContentWidth(Element* element, const Box& initial_box, const FormattingMode& formatting_mode)
+{
+	RMLUI_ASSERT(formatting_mode.constraint == FormattingMode::Constraint::MaxContent);
+	const Vector2f containing_block(-1.f);
+	RootBox root(Box(containing_block), formatting_mode);
+
+	const Vector2f min_flex_size(0.f);
+	const Vector2f max_flex_size(FLT_MAX);
+	UniquePtr<LayoutBox> layout_box = FlexFormattingContext::FormatImpl(&root, element, initial_box, min_flex_size, max_flex_size);
+
+	return layout_box->GetShrinkToFitWidth();
+}
+
+UniquePtr<LayoutBox> FlexFormattingContext::FormatImpl(ContainerBox* parent_container, Element* element, const Box& initial_box,
+	Vector2f flex_min_size, Vector2f flex_max_size)
 {
 	RMLUI_ZoneScopedC(0xAFAF4F);
 	auto flex_container_box = MakeUnique<FlexContainer>(element, parent_container, initial_box);
 
 	ElementScroll* element_scroll = element->GetElementScroll();
-	const ComputedValues& computed = element->GetComputedValues();
-
-	const FormattingMode::Constraint formatting_constraint = parent_container->GetFormattingMode().constraint;
-	RMLUI_ASSERT(containing_block.x >= 0.f || formatting_constraint == FormattingMode::Constraint::MaxContent);
 
-	// if (formatting_constraint == FormattingMode::Constraint::MaxContent)
-	// 	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock);
 	Box& box = flex_container_box->GetBox();
 
 	// Start with any auto-scrollbars off.
@@ -62,18 +81,8 @@ UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_containe
 	FlexFormattingContext context;
 	context.flex_container_box = flex_container_box.get();
 	context.element_flex = element;
-
-	if (formatting_constraint == FormattingMode::Constraint::MaxContent)
-	{
-		// Format max-content as if unconstrained, clamping is instead done later during normal formatting.
-		context.flex_min_size = Vector2f(0.f);
-		context.flex_max_size = Vector2f(FLT_MAX);
-	}
-	else
-	{
-		LayoutDetails::GetMinMaxWidth(context.flex_min_size.x, context.flex_max_size.x, computed, box, containing_block.x);
-		LayoutDetails::GetMinMaxHeight(context.flex_min_size.y, context.flex_max_size.y, computed, box, containing_block.y);
-	}
+	context.flex_min_size = flex_min_size;
+	context.flex_max_size = flex_max_size;
 
 	const Vector2f box_content_size = box.GetSize(); // Can be negative for auto size (infinite available space).
 	context.flex_content_offset = box.GetPosition();

+ 6 - 0
Source/Core/Layout/FlexFormattingContext.h

@@ -47,7 +47,13 @@ public:
 	/// Formats a flex container element and its flex items according to flexbox layout rules.
 	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, Vector2f containing_block, const Box& initial_box);
 
+	/// Formats a flex container to determine its max-content width.
+	static float DetermineMaxContentWidth(Element* element, const Box& initial_box, const FormattingMode& formatting_mode);
+
 private:
+	static UniquePtr<LayoutBox> FormatImpl(ContainerBox* parent_container, Element* element, const Box& initial_box, Vector2f flex_min_size,
+		Vector2f flex_max_size);
+
 	FlexFormattingContext() = default;
 
 	/// Format the flexbox and its children.

+ 56 - 3
Source/Core/Layout/FormattingContext.cpp

@@ -77,6 +77,9 @@ UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_c
 	if (type == FormattingContextType::None)
 		type = default_context;
 
+	if (type == FormattingContextType::None)
+		return nullptr;
+
 	if (element->GetId() == "outer")
 		int x = 0;
 
@@ -129,12 +132,62 @@ UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_c
 		const Vector2f containing_block = parent_container->GetContainingBlockSize(element->GetPosition());
 		Box box;
 		if (override_initial_box)
+		{
 			box = *override_initial_box;
-
+		}
 		else if (formatting_mode.constraint == FormattingMode::Constraint::MaxContent && type == FormattingContextType::Flex)
+		{
 			LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock);
+		}
 		else
-			LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::ShrinkableBlock, &parent_container->GetFormattingMode());
+		{
+			LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::ShrinkableBlock);
+
+			// Check for shrink-to-fit width.
+			if (box.GetSize().x < 0.f)
+			{
+				const float box_height = box.GetSize().y;
+
+				// Max-content width should be calculated without any vertical constraint.
+				box.SetContent(Vector2f(-1.f));
+
+				FormattingMode shrink_formatting_mode = formatting_mode;
+				shrink_formatting_mode.constraint = FormattingMode::Constraint::MaxContent;
+
+				float max_content_width = -1.f;
+				switch (type)
+				{
+				case FormattingContextType::Block:
+					max_content_width = BlockFormattingContext::DetermineMaxContentWidth(element, box, shrink_formatting_mode);
+					break;
+				case FormattingContextType::Table:
+					// Currently we don't support shrink-to-fit width for tables, just use a zero-sized width.
+					max_content_width = 0.f;
+					break;
+				case FormattingContextType::Flex:
+					max_content_width = FlexFormattingContext::DetermineMaxContentWidth(element, box, shrink_formatting_mode);
+					break;
+				case FormattingContextType::Replaced: RMLUI_ERRORMSG("Replaced elements are expected to have a positive intrinsice size."); break;
+				case FormattingContextType::None: RMLUI_ERROR; break;
+				}
+
+				RMLUI_ASSERTMSG(max_content_width >= 0.f, "Max-content width should evaluate to a positive size.")
+
+				// TODO: Cache max_content width. Remove LayoutDetails::ShrinkToFit (or re-use this function here).
+				// Maybe split this out to a separate function that can be called also from e.g. FlexBoxFormatting.
+
+				float fit_content_width = max_content_width;
+				if (containing_block.x >= 0.f)
+				{
+					const float available_width =
+						Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding));
+					fit_content_width = Math::Min(max_content_width, available_width);
+				}
+
+				box.SetContent(Vector2f(fit_content_width, box_height));
+				LayoutDetails::ClampSizeAndBuildAutoMarginsForBlockWidth(box, containing_block, element);
+			}
+		}
 
 		switch (type)
 		{
@@ -142,7 +195,7 @@ UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_c
 		case FormattingContextType::Table: layout_box = TableFormattingContext::Format(parent_container, element, containing_block, box); break;
 		case FormattingContextType::Flex: layout_box = FlexFormattingContext::Format(parent_container, element, containing_block, box); break;
 		case FormattingContextType::Replaced: layout_box = ReplacedFormattingContext::Format(parent_container, element, box); break;
-		case FormattingContextType::None: break;
+		case FormattingContextType::None: RMLUI_ERROR; break;
 		}
 
 		// TODO: If MaxContent constraint, and containing block = -1, -1, store resulting size as max-content size.

+ 27 - 22
Source/Core/Layout/LayoutDetails.cpp

@@ -50,10 +50,9 @@ static inline float BorderSizeToContentSize(float border_size, float border_padd
 	return Math::Max(0.0f, border_size - border_padding_edges_size);
 }
 
-void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_mode, const FormattingMode* formatting_mode)
+void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_mode)
 {
 	// A shrinkable block may start formatting, thus the current formatting mode must be provided.
-	RMLUI_ASSERT(box_mode != BuildBoxMode::ShrinkableBlock || formatting_mode != nullptr);
 	RMLUI_ZoneScoped;
 
 	if (!element)
@@ -124,14 +123,16 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 			content_area.y = Math::Clamp(content_area.y, min_size.y, max_size.y);
 
 		if (replaced_element)
+		{
 			content_area = CalculateSizeForReplacedElement(content_area, min_size, max_size, intrinsic_size, intrinsic_ratio);
+			RMLUI_ASSERTMSG(content_area.x >= 0 && content_area.y >= 0, "Replaced elements are expected to have a positive intrinsic size.");
+		}
 	}
 
 	box.SetContent(content_area);
 
 	// Evaluate the margins, and width and height if they are auto.
-	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, box_mode, replaced_element,
-		(box_mode == BuildBoxMode::ShrinkableBlock ? formatting_mode : nullptr));
+	return BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, box_mode);
 }
 
 void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width)
@@ -184,12 +185,12 @@ void LayoutDetails::BuildAutoMarginsForBlockBox(Box& box, Vector2f containing_bl
 	const Vector2f min_size = {0, 0};
 	const Vector2f max_size = {FLT_MAX, FLT_MAX};
 
-	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, BuildBoxMode::Block, true, nullptr);
+	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, BuildBoxMode::Block);
 	RMLUI_ASSERT(box.GetSize() == initial_content_size);
 }
 
 void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element,
-	BuildBoxMode box_mode, bool replaced_element, const FormattingMode* formatting_mode)
+	BuildBoxMode box_mode)
 {
 	const ComputedValues& computed = element->GetComputedValues();
 
@@ -205,11 +206,22 @@ void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f
 	else
 	{
 		// The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and dimensions.
-		BuildBoxWidth(box, computed, min_size.x, max_size.x, containing_block, element, replaced_element, formatting_mode);
+		BuildBoxWidth(box, computed, min_size.x, max_size.x, containing_block, element);
 		BuildBoxHeight(box, computed, min_size.y, max_size.y, containing_block.y);
 	}
 }
 
+void LayoutDetails::ClampSizeAndBuildAutoMarginsForBlockWidth(Box& box, Vector2f containing_block, Element* element)
+{
+	RMLUI_ASSERT(box.GetSize().x >= 0.f);
+	const ComputedValues& computed = element->GetComputedValues();
+
+	float min_width = 0.f;
+	float max_width = FLT_MAX;
+	GetMinMaxWidth(min_width, max_width, computed, box, containing_block.x);
+	BuildBoxWidth(box, computed, min_width, max_width, containing_block, element);
+}
+
 float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_block, const FormattingMode& current_formatting_mode)
 {
 	RMLUI_ASSERT(element);
@@ -224,10 +236,7 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	// shrink-to-fit width first? Use a non-definite placeholder for the box content width, and available width as a
 	// maximum constraint.
 	Box box;
-	float min_height, max_height;
 	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock);
-	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
-
 	if (box.GetSize().x >= 0.f)
 	{
 		return box.GetSize().x;
@@ -405,7 +414,7 @@ Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified
 }
 
 void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block,
-	Element* element, bool replaced_element, const FormattingMode* formatting_mode, float override_shrink_to_fit_width)
+	Element* element)
 {
 	Vector2f content_area = box.GetSize();
 
@@ -445,9 +454,8 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 	{
 		// 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 = formatting_mode != nullptr && !replaced_element &&
-			((computed.float_() != Style::Float::None) || (absolutely_positioned && inset_auto) ||
-				(computed.display() == Style::Display::InlineBlock || computed.display() == Style::Display::InlineFlex));
+		const bool shrink_to_fit = ((computed.float_() != Style::Float::None) || (absolutely_positioned && inset_auto) ||
+			(computed.display() == Style::Display::InlineBlock || computed.display() == Style::Display::InlineFlex));
 
 		if (!shrink_to_fit)
 		{
@@ -455,14 +463,9 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 			const float accumulated_edges = GetInsetWidth() + box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding);
 			content_area.x = Math::Max(containing_block.x - accumulated_edges, 0.f);
 		}
-		else if (override_shrink_to_fit_width >= 0)
-		{
-			content_area.x = override_shrink_to_fit_width;
-		}
 		else
 		{
-			content_area.x = GetShrinkToFitWidth(element, containing_block, *formatting_mode);
-			override_shrink_to_fit_width = content_area.x;
+			// Leave width negative to indicate it should have its shrink-to-fit width evaluated.
 		}
 	}
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
@@ -480,16 +483,18 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 	// 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.
 	const float clamped_width = Math::Clamp(content_area.x, min_width, max_width);
-	if (clamped_width != content_area.x)
+	if (content_area.x >= 0 && clamped_width != content_area.x)
 	{
 		content_area.x = clamped_width;
 		box.SetContent(content_area);
 
 		if (num_auto_margins > 0)
-			BuildBoxWidth(box, computed, min_width, max_width, containing_block, element, replaced_element, formatting_mode, clamped_width);
+			BuildBoxWidth(box, computed, min_width, max_width, containing_block, element);
 	}
 	else
+	{
 		box.SetContent(content_area);
+	}
 }
 
 void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, float min_height, float max_height, float containing_block_height)

+ 12 - 14
Source/Core/Layout/LayoutDetails.h

@@ -54,8 +54,8 @@ struct ComputedAxisSize {
 
 enum class BuildBoxMode {
 	Block, // Sets edges and size if available, auto width uses stretch-fit width (never shrink-to-fit), auto margins are used for alignment.
-	ShrinkableBlock, // Like block, but uses shrink-to-fit width when appropriate, determined by formatting the element.
-	UnalignedBlock,  // Like block, but auto width returns -1, and auto margins are resolved to zero.
+	ShrinkableBlock, // Like block, but auto width returns -1 when shrink-to-fit width should be applied, auto margins should then be re-evaluated.
+	UnalignedBlock,  // Like block, but auto width returns -1, and auto margins are always resolved to zero.
 	Inline,          // Sets edges, ignores width, height, and auto margins.
 };
 
@@ -71,9 +71,7 @@ public:
 	/// @param[in] containing_block The dimensions of the content area of the block containing the element.
 	/// @param[in] element The element to build the box for.
 	/// @param[in] box_mode The mode which determines how the box is built.
-	/// @param[in] formatting_mode Active formatting mode, must be set when using box mode ShrinkableBlock.
-	static void BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_mode,
-		const FormattingMode* formatting_mode = nullptr);
+	static void BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_mode);
 
 	// Retrieves the minimum and maximum width from an element's computed values.
 	static void GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width);
@@ -92,6 +90,12 @@ public:
 	/// @param[in] element The element the box is being generated for.
 	static void BuildAutoMarginsForBlockBox(Box& box, Vector2f containing_block, Element* element);
 
+	/// Clamps the width and build box margins for a block box, the other edges are already set.
+	/// @param[in,out] box The box to generate.
+	/// @param[in] containing_block The size of the containing block.
+	/// @param[in] element The element the box is being generated for.
+	static void ClampSizeAndBuildAutoMarginsForBlockWidth(Box& box, Vector2f containing_block, Element* element);
+
 	/// Formats the element and returns the width of its contents.
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block, const FormattingMode& current_formatting_mode);
 
@@ -120,10 +124,8 @@ private:
 	/// @param[in] containing_block The size of the containing block.
 	/// @param[in] element The element the box is being generated for.
 	/// @param[in] box_mode The mode which determines how the box is built.
-	/// @param[in] replaced_element True when the element is a replaced element.
-	/// @param[in] formatting_mode Active formatting mode, must be set when using ShrinkableBlock box context.
 	static void BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element,
-		BuildBoxMode box_mode, bool replaced_element, const FormattingMode* formatting_mode);
+		BuildBoxMode box_mode);
 
 	/// Calculates and returns the content size for replaced elements.
 	static Vector2f CalculateSizeForReplacedElement(Vector2f specified_content_size, Vector2f min_size, Vector2f max_size, Vector2f intrinsic_size,
@@ -137,12 +139,8 @@ private:
 	/// @param[in] max_width The maximum content width of the element.
 	/// @param[in] containing_block The size of the containing block.
 	/// @param[in] element The element the box is being generated for.
-	/// @param[in] replaced_element True when the element is a replaced element.
-	/// @param[in] formatting_mode Active formatting mode, must be set when using ShrinkableBlock box context.
-	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow
-	/// shrinking.
-	static void BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block, Element* element,
-		bool replaced_element, const FormattingMode* formatting_mode, float override_shrink_to_fit_width = -1);
+	static void BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block,
+		Element* element);
 	/// Builds the block-specific height and vertical margins of a Box.
 	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the height
 	/// property, and -1 means auto height.