Selaa lähdekoodia

Compute shrink-to-fit width for flex blocks (#559)

Refs https://github.com/mikke89/RmlUi/issues/552
Alexandr Lurye 1 vuosi sitten
vanhempi
sitoutus
5961ff5c42

+ 4 - 3
Source/Core/Layout/ContainerBox.cpp

@@ -31,6 +31,7 @@
 #include "../../../Include/RmlUi/Core/Element.h"
 #include "../../../Include/RmlUi/Core/ElementScroll.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
+#include "FlexFormattingContext.h"
 #include "FormattingContext.h"
 #include "LayoutDetails.h"
 #include <algorithm>
@@ -269,12 +270,12 @@ bool FlexContainer::Close(const Vector2f content_overflow_size, const Box& box,
 
 float FlexContainer::GetShrinkToFitWidth() const
 {
-	// We don't currently support shrink-to-fit layout of flex containers. However, for the trivial case of a fixed
-	// width, we simply return that.
+	// For the trivial case of a fixed width, we simply return that.
 	if (element->GetComputedValues().width().type == Style::Width::Type::Length)
 		return box.GetSize().x;
 
-	return 0.0f;
+	// Infer shrink-to-fit width from the intrinsic width of the element.
+	return FlexFormattingContext::GetMaxContentSize(element).x;
 }
 
 String FlexContainer::DebugDumpTree(int depth) const

+ 26 - 3
Source/Core/Layout/FlexFormattingContext.cpp

@@ -118,6 +118,28 @@ UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_containe
 	return flex_container_box;
 }
 
+Vector2f FlexFormattingContext::GetMaxContentSize(Element* element)
+{
+	// A large but finite number is used here, because the flexbox formatting algorithm
+	// needs to round numbers, and it doesn't support infinities.
+	const Vector2f infinity(10000.0f, 10000.0f);
+	RootBox root(infinity);
+	auto flex_container_box = MakeUnique<FlexContainer>(element, &root);
+
+	FlexFormattingContext context;
+	context.flex_container_box = flex_container_box.get();
+	context.element_flex = element;
+	context.flex_available_content_size = Vector2f(-1, -1);
+	context.flex_content_containing_block = infinity;
+	context.flex_max_size = Vector2f(FLT_MAX, FLT_MAX);
+
+	// Format the flexbox and all its children.
+	Vector2f flex_resulting_content_size, content_overflow_size;
+	float flex_baseline = 0.f;
+	context.Format(flex_resulting_content_size, content_overflow_size, flex_baseline);
+	return flex_resulting_content_size;
+}
+
 struct FlexItem {
 	// In the following, suffix '_a' means flex start edge while '_b' means flex end edge.
 	struct Size {
@@ -137,7 +159,7 @@ struct FlexItem {
 	Size cross;
 	float flex_shrink_factor;
 	float flex_grow_factor;
-	Style::AlignSelf align_self; // 'Auto' is replaced by container's 'align-items' value
+	Style::AlignSelf align_self;  // 'Auto' is replaced by container's 'align-items' value
 
 	float inner_flex_base_size;   // Inner size
 	float flex_base_size;         // Outer size
@@ -315,10 +337,11 @@ void FlexFormattingContext::Format(Vector2f& flex_resulting_content_size, Vector
 			RMLUI_ASSERT(initial_box_size.y < 0.f);
 
 			Box format_box = item.box;
-			if (initial_box_size.x < 0.f)
+			if (initial_box_size.x < 0.f && flex_available_content_size.x >= 0.f)
 				format_box.SetContent(Vector2f(flex_available_content_size.x - item.cross.sum_edges, initial_box_size.y));
 
-			FormattingContext::FormatIndependent(flex_container_box, element, &format_box, FormattingContextType::Block);
+			FormattingContext::FormatIndependent(flex_container_box, element, (format_box.GetSize().x >= 0 ? &format_box : nullptr),
+				FormattingContextType::Block);
 			item.inner_flex_base_size = element->GetBox().GetSize().y;
 		}
 

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

@@ -43,8 +43,12 @@ class FlexContainer;
 */
 class FlexFormattingContext final : public FormattingContext {
 public:
+	/// Formats a flex container element and its flex items according to flexbox layout rules.
 	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box);
 
+	/// Computes max-content size for a flex container.
+	static Vector2f GetMaxContentSize(Element* element);
+
 private:
 	FlexFormattingContext() = default;
 

+ 14 - 8
Source/Core/Layout/LayoutDetails.cpp

@@ -256,16 +256,17 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock);
 	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 
-	// Currently we don't support shrink-to-fit width for flexboxes or tables. Just return a zero-sized width.
+	// Currently we don't support shrink-to-fit width for tables. Just return a zero-sized width.
 	const Style::Display display = element->GetDisplay();
-	if (display == Style::Display::Flex || display == Style::Display::InlineFlex || display == Style::Display::Table ||
-		display == Style::Display::InlineTable)
+	if (display == Style::Display::Table || display == Style::Display::InlineTable)
+	{
 		return 0.f;
+	}
 
 	// Use a large size for the box content width, so that it is practically unconstrained. This makes the formatting
 	// procedure act as if under a maximum content constraint. Children with percentage sizing values may be scaled
 	// based on this width (such as 'width' or 'margin'), if so, the layout is considered undefined like in CSS 2.
-	const float max_content_constraint_width = containing_block.x + 1000.f;
+	const float max_content_constraint_width = containing_block.x + 10000.f;
 	box.SetContent({max_content_constraint_width, box.GetSize().y});
 
 	// First, format the element under the above generated box. Then we ask the resulting box for its shrink-to-fit
@@ -275,9 +276,14 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	RootBox root(Math::Max(containing_block, Vector2f(0.f)));
 	UniquePtr<LayoutBox> layout_box = FormattingContext::FormatIndependent(&root, element, &box, FormattingContextType::Block);
 
-	const float available_width = Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding));
-
-	return Math::Min(available_width, layout_box->GetShrinkToFitWidth());
+	float shrink_to_fit_width = layout_box->GetShrinkToFitWidth();
+	if (containing_block.x >= 0)
+	{
+		const float available_width =
+			Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding));
+		shrink_to_fit_width = Math::Min(shrink_to_fit_width, available_width);
+	}
+	return shrink_to_fit_width;
 }
 
 ComputedAxisSize LayoutDetails::BuildComputedHorizontalSize(const ComputedValues& computed)
@@ -446,7 +452,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		// 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) || (absolutely_positioned && inset_auto) ||
-				(computed.display() == Style::Display::InlineBlock));
+				(computed.display() == Style::Display::InlineBlock || computed.display() == Style::Display::InlineFlex));
 
 		if (!shrink_to_fit)
 		{