Commit f0998e38 authored by Dana Fried's avatar Dana Fried Committed by Commit Bot

FlexLayout distributes space optimally.

This changes long-standing problems with FlexLayout that were not
problems with BoxLayout. Specifically:
 - Flex weights now dictate deviation from a view's preferred size
   (which may now be zero), so that when a view with a FlexLayout
   shrinks, its flex children shrink proportionately, just as they grow
   proportionately when the host view enlarges.
 - When a child view hits its minimum size or drops out, remaining space
   is correctly allocated across other child views, even at the same
   flex priority.

This solves a number of weird corner cases and rounding issues at the
result of a bit more complexity. However, the code is more heavily
documented and broken into steps so the net effect is that it should be
relatively maintainable.

Bug: 1012134, 1012113

Change-Id: Idc3059d572b3d165866b44c077c38d483d862596
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2100026
Commit-Queue: Dana Fried <dfried@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809245}
parent 95537b8a
...@@ -211,12 +211,10 @@ void AssistantOnboardingSuggestionView::InitLayout( ...@@ -211,12 +211,10 @@ void AssistantOnboardingSuggestionView::InitLayout(
label_->SetLineHeight(kLabelLineHeight); label_->SetLineHeight(kLabelLineHeight);
label_->SetMaxLines(2); label_->SetMaxLines(2);
label_->SetMultiLine(true); label_->SetMultiLine(true);
label_->SetPreferredSize(gfx::Size(INT_MAX, INT_MAX));
label_->SetProperty( label_->SetProperty(
views::kFlexBehaviorKey, views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded, views::MaximumFlexSizeRule::kUnbounded));
/*adjust_height_for_width=*/true));
label_->SetText(base::UTF8ToUTF16(suggestion.text)); label_->SetText(base::UTF8ToUTF16(suggestion.text));
} }
......
...@@ -4146,8 +4146,8 @@ TEST_F(AnimatingLayoutManagerInFlexLayoutTest, NoAnimation) { ...@@ -4146,8 +4146,8 @@ TEST_F(AnimatingLayoutManagerInFlexLayoutTest, NoAnimation) {
const gfx::Size preferred = target_layout()->GetPreferredSize(view()); const gfx::Size preferred = target_layout()->GetPreferredSize(view());
root_view()->SetSize(preferred); root_view()->SetSize(preferred);
layout()->ResetLayout(); layout()->ResetLayout();
root_view()->Layout();
AnimationEventLogger logger(layout()); AnimationEventLogger logger(layout());
root_view()->Layout();
EXPECT_EQ(preferred, view()->size()); EXPECT_EQ(preferred, view()->size());
const std::vector<bool> expected_events{}; const std::vector<bool> expected_events{};
EXPECT_EQ(expected_events, logger.events()); EXPECT_EQ(expected_events, logger.events());
......
...@@ -155,12 +155,10 @@ class SimulatedExtensionsContainer : public SimulatedToolbarElement { ...@@ -155,12 +155,10 @@ class SimulatedExtensionsContainer : public SimulatedToolbarElement {
main_button->SetProperty(kFlexBehaviorKey, FlexSpecification()); main_button->SetProperty(kFlexBehaviorKey, FlexSpecification());
layout()->SetDefaultFadeMode( layout()->SetDefaultFadeMode(
AnimatingLayoutManager::FadeInOutMode::kSlideFromTrailingEdge); AnimatingLayoutManager::FadeInOutMode::kSlideFromTrailingEdge);
target_layout() target_layout()->SetDefault(
->SetFlexAllocationOrder(FlexAllocationOrder::kReverse) kFlexBehaviorKey,
.SetDefault( FlexSpecification(LayoutOrientation::kHorizontal,
kFlexBehaviorKey, MinimumFlexSizeRule::kPreferredSnapToZero));
FlexSpecification(LayoutOrientation::kHorizontal,
MinimumFlexSizeRule::kPreferredSnapToZero));
} }
~SimulatedExtensionsContainer() override = default; ~SimulatedExtensionsContainer() override = default;
......
This diff is collapsed.
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_ #ifndef UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
#define UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_ #define UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
#include <list>
#include <map> #include <map>
#include <memory> #include <memory>
#include <set> #include <set>
...@@ -146,10 +147,12 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -146,10 +147,12 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
FlexLayout* const layout_; FlexLayout* const layout_;
}; };
using ChildIndices = std::list<size_t>;
// Maps a flex order (lower = allocated first, and therefore higher priority) // Maps a flex order (lower = allocated first, and therefore higher priority)
// to the indices of child views within that order that can flex. // to the indices of child views within that order that can flex.
// See FlexSpecification::order(). // See FlexSpecification::order().
using FlexOrderToViewIndexMap = std::map<int, std::vector<size_t>>; using FlexOrderToViewIndexMap = std::map<int, ChildIndices>;
// Returns the preferred size for a given |rule| and |child| given unbounded // Returns the preferred size for a given |rule| and |child| given unbounded
// space, with the caveat that for vertical layouts the horizontal axis is // space, with the caveat that for vertical layouts the horizontal axis is
...@@ -166,25 +169,6 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -166,25 +169,6 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
const View* child, const View* child,
const NormalizedSizeBounds& available) const; const NormalizedSizeBounds& available) const;
// Fills out the child entries for |data| and generates some initial size
// and visibility data, and stores off information about which views can
// expand in |flex_order_to_index|.
void InitializeChildData(const NormalizedSizeBounds& bounds,
FlexLayoutData* data,
FlexOrderToViewIndexMap* flex_order_to_index) const;
// Caclulates the child bounds (in screen coordinates) for each visible child
// in the layout.
void CalculateChildBounds(const SizeBounds& size_bounds,
FlexLayoutData* data) const;
// Calculates available space for non-flex views.
void CalculateNonFlexAvailableSpace(
FlexLayoutData* data,
const SizeBound& available_space,
const ChildViewSpacing& child_spacing,
const FlexOrderToViewIndexMap& flex_views) const;
// Returns the combined margins across the cross axis of the host view, for a // Returns the combined margins across the cross axis of the host view, for a
// particular child view. // particular child view.
Inset1D GetCrossAxisMargins(const FlexLayoutData& layout, Inset1D GetCrossAxisMargins(const FlexLayoutData& layout,
...@@ -211,24 +195,116 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -211,24 +195,116 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// Calculates the position of each child view and the size of the overall // Calculates the position of each child view and the size of the overall
// layout based on tentative visibilities and sizes for each child. // layout based on tentative visibilities and sizes for each child.
void UpdateLayoutFromChildren(const NormalizedSizeBounds& bounds, void UpdateLayoutFromChildren(const NormalizedSizeBounds& bounds,
FlexLayoutData* data, FlexLayoutData& data,
ChildViewSpacing* child_spacing) const; ChildViewSpacing& child_spacing) const;
// Fills out the child entries for |data| and generates some initial size
// and visibility data, and stores off information about which views can
// expand in |flex_order_to_index|.
void InitializeChildData(const NormalizedSizeBounds& bounds,
FlexLayoutData& data,
FlexOrderToViewIndexMap& flex_order_to_index) const;
// Caclulates the child bounds (in screen coordinates) for each visible child
// in the layout.
void CalculateChildBounds(const SizeBounds& size_bounds,
FlexLayoutData& data) const;
// Calculates available space along the main axis for non-flex views and
// the values in |data.child_data|.
void CalculateNonFlexAvailableSpace(int available_space,
const FlexOrderToViewIndexMap& flex_views,
const ChildViewSpacing& child_spacing,
FlexLayoutData& data) const;
// Applies flex rules to each view in a layout, updating |data| and // Allocates space shortage (when the available space is less than the
// |child_spacing|. // preferred size of the layout) across child views that can flex.
//
// Updates are made to |data| and |child_spacing|, and views that can still
// expand above their preferred size are added to |expandable_views| for later
// processing by AllocateFlexExcess().
void AllocateFlexShortage(const NormalizedSizeBounds& bounds,
const FlexOrderToViewIndexMap& order_to_index,
FlexLayoutData& data,
ChildViewSpacing& child_spacing,
FlexOrderToViewIndexMap& expandable_views) const;
// Allocates space above each child view's preferred size, based on remaining/
// excess space in the layout.
void AllocateFlexExcess(const NormalizedSizeBounds& bounds,
const FlexOrderToViewIndexMap& order_to_index,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const;
// Updates the available space for each flex child in |child_indices| in
// |data.child_data| based on |data.total_size|, |bounds|, and the margin data
// in |child_spacing|.
void CalculateFlexAvailableSpace(const NormalizedSizeBounds& bounds,
const ChildIndices& child_indices,
const ChildViewSpacing& child_spacing,
FlexLayoutData& data) const;
// Pre-allocates space associated with zero-weight views at a particular flex
// priority |flex_order|. Zero-weight child views are removed from
// |child_list| and their entries are updated in |data|. If |expandable_views|
// is specified, this is treated as the first pass, and space allocated to
// each view is capped at its preferred size; if the view would claim more
// space it is added to |expandable_views| (if specified).
void AllocateZeroWeightFlex(const NormalizedSizeBounds& bounds,
int flex_order,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing,
FlexOrderToViewIndexMap* expandable_views) const;
// Tries to allocate all the views in |child_list| in the available |bounds|.
// If successful, returns true and updates |data| and |expandable_views|.
// |remaining_deficit| is set to the difference between the space needed by
// all of the views in |child_list| and the space provided by |bounds|.
bool TryAllocateAll(const NormalizedSizeBounds& bounds,
int flex_order,
const ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing,
FlexOrderToViewIndexMap& expandable_views,
int& remaining_deficit) const;
// Allocates flex excess |to_allocate| for a list of child views at the same
// priority order.
//
// It will attempt to do the entire allocation in one pass, removing all
// elements from |child_list| that it successfully allocates space for, but in
// the event a member of |child_list| does not take its full allocation, it
// will remove just that child and set aside its smaller size. At least one
// child will always be removed and |to_allocate| will be updated with the
// remaining space in the layout.
//
// This method should be called repeatedly until |child_list| is empty.
void AllocateFlexExcessAtOrder(const NormalizedSizeBounds& bounds,
int& to_allocate,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const;
// Allocates flex shortage for a list of child views at priority |order|.
// //
// If |expandable_views| is specified, any view requesting more than its // It will attempt to allocate the entire |child_list| in one pass, removing
// preferred size will be clamped to its preferred size and be added to // all elements that it successfully allocates space for, but in the event one
// |expandable_views| for later processing after all other flex space has been // or more members of |child_list| do not take their full allocation, those
// allocated. // views will be allocated and removed from the list, and this method should
// be called again on the new, smaller list. At least one child is guaranteed
// to be allocated and removed each invocation.
// //
// Typically, this method will be called once with |expandable_views| set and // This method should be called repeatedly until |child_list| is empty.
// then again with it null to allocate the remaining space. void AllocateFlexShortageAtOrder(const NormalizedSizeBounds& bounds,
void AllocateFlexSpace(const NormalizedSizeBounds& bounds, int deficit,
const FlexOrderToViewIndexMap& order_to_index, ChildIndices& child_list,
FlexLayoutData* data, FlexLayoutData& data,
ChildViewSpacing* child_spacing, ChildViewSpacing& child_spacing) const;
FlexOrderToViewIndexMap* expandable_views) const;
// Returns the total weight for all children listed in |child_indices|.
static int CalculateFlexTotal(const FlexLayoutData& data,
const ChildIndices& child_indices);
// Gets the default value for a particular layout property, which will be used // Gets the default value for a particular layout property, which will be used
// if the property is not set on a child view being laid out (e.g. // if the property is not set on a child view being laid out (e.g.
......
...@@ -175,10 +175,7 @@ FlexSpecification::FlexSpecification(MinimumFlexSizeRule minimum_size_rule, ...@@ -175,10 +175,7 @@ FlexSpecification::FlexSpecification(MinimumFlexSizeRule minimum_size_rule,
maximum_size_rule, maximum_size_rule,
minimum_size_rule, minimum_size_rule,
maximum_size_rule, maximum_size_rule,
adjust_height_for_width)) { adjust_height_for_width)) {}
unlimited_main_axis_size_ =
maximum_size_rule == MaximumFlexSizeRule::kUnbounded;
}
FlexSpecification::FlexSpecification( FlexSpecification::FlexSpecification(
LayoutOrientation orientation, LayoutOrientation orientation,
...@@ -199,10 +196,7 @@ FlexSpecification::FlexSpecification( ...@@ -199,10 +196,7 @@ FlexSpecification::FlexSpecification(
orientation == LayoutOrientation::kVertical orientation == LayoutOrientation::kVertical
? maximum_main_axis_rule ? maximum_main_axis_rule
: kDefaultMaximumFlexSizeRule, : kDefaultMaximumFlexSizeRule,
adjust_height_for_width)) { adjust_height_for_width)) {}
unlimited_main_axis_size_ =
maximum_main_axis_rule == MaximumFlexSizeRule::kUnbounded;
}
FlexSpecification::FlexSpecification(const FlexSpecification& other) = default; FlexSpecification::FlexSpecification(const FlexSpecification& other) = default;
......
...@@ -152,14 +152,12 @@ class VIEWS_EXPORT FlexSpecification { ...@@ -152,14 +152,12 @@ class VIEWS_EXPORT FlexSpecification {
int weight() const { return weight_; } int weight() const { return weight_; }
int order() const { return order_; } int order() const { return order_; }
LayoutAlignment alignment() const { return alignment_; } LayoutAlignment alignment() const { return alignment_; }
bool unlimited_main_axis_size() const { return unlimited_main_axis_size_; }
private: private:
FlexRule rule_; FlexRule rule_;
int order_ = 1; int order_ = 1;
int weight_ = 0; int weight_ = 0;
LayoutAlignment alignment_ = LayoutAlignment::kStretch; LayoutAlignment alignment_ = LayoutAlignment::kStretch;
bool unlimited_main_axis_size_ = false;
}; };
// Represents insets in a single dimension. // Represents insets in a single dimension.
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment