Skip to content

Commit

Permalink
Compute shrink-to-fit width for flex blocks
Browse files Browse the repository at this point in the history
Refs #552
  • Loading branch information
alml committed Jan 18, 2024
1 parent 8456240 commit 8f1d503
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 14 deletions.
7 changes: 4 additions & 3 deletions Source/Core/Layout/ContainerBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down
29 changes: 26 additions & 3 deletions Source/Core/Layout/FlexFormattingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 4 additions & 0 deletions Source/Core/Layout/FlexFormattingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
22 changes: 14 additions & 8 deletions Source/Core/Layout/LayoutDetails.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit 8f1d503

Please sign in to comment.