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

FlexLayout: configure how host view insets and interior margins are used

This CL adds two new ways to specify edge case behavior for FlexLayout
that will make some use cases much easier.

1. SetTargetArea(LayoutTargetArea) - specifies whether the host view's
insets are treated as inviolable (default and older behavior) or whether
they're considered part of the internal margins of the host view for
layout purposes. The new (optional) behavior allows certain cases where
a child view with internal padding cannot currently get close enough to
a host view border, inkdrop, or focus ring.

2. Set[Main|Cross]AxisMarginsPolicy(LayoutMarginPolicy) - specifies
whether child margins apply when they abut the edge of the host view.
The new (optional) behavior allows much simpler code when spacing between
views needs to be different than spacing at the edges of the host view.

OPEN QUESTIONS:
 - Should we just make the new behavior for (1) the default?

Bug: 898632
Change-Id: I5d336ed19b32a791f944f8ced615a099a0729802
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1676736
Commit-Queue: Dana Fried <dfried@chromium.org>
Reviewed-by: default avatarPeter Boström <pbos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683293}
parent a5956e5c
...@@ -50,6 +50,7 @@ struct FlexChildData { ...@@ -50,6 +50,7 @@ struct FlexChildData {
NormalizedSize preferred_size; NormalizedSize preferred_size;
NormalizedSize current_size; NormalizedSize current_size;
NormalizedInsets margins; NormalizedInsets margins;
bool using_default_margins = true;
NormalizedInsets internal_padding; NormalizedInsets internal_padding;
NormalizedRect actual_bounds; NormalizedRect actual_bounds;
FlexSpecification flex; FlexSpecification flex;
...@@ -63,10 +64,16 @@ struct FlexChildData { ...@@ -63,10 +64,16 @@ struct FlexChildData {
template <typename T> template <typename T>
T GetViewProperty(const View* view, T GetViewProperty(const View* view,
const ui::PropertyHandler& defaults, const ui::PropertyHandler& defaults,
const ui::ClassProperty<T*>* property) { const ui::ClassProperty<T*>* property,
bool* is_default = nullptr) {
T* found_value = view->GetProperty(property); T* found_value = view->GetProperty(property);
if (found_value) if (found_value) {
if (is_default)
*is_default = false;
return *found_value; return *found_value;
}
if (is_default)
*is_default = true;
found_value = defaults.GetProperty(property); found_value = defaults.GetProperty(property);
if (found_value) if (found_value)
return *found_value; return *found_value;
...@@ -257,6 +264,15 @@ FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) { ...@@ -257,6 +264,15 @@ FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) {
return *this; return *this;
} }
FlexLayout& FlexLayout::SetIncludeHostInsetsInLayout(
bool include_host_insets_in_layout) {
if (include_host_insets_in_layout != include_host_insets_in_layout_) {
include_host_insets_in_layout_ = include_host_insets_in_layout;
InvalidateLayout();
}
return *this;
}
FlexLayout& FlexLayout::SetCollapseMargins(bool collapse_margins) { FlexLayout& FlexLayout::SetCollapseMargins(bool collapse_margins) {
if (collapse_margins != collapse_margins_) { if (collapse_margins != collapse_margins_) {
collapse_margins_ = collapse_margins; collapse_margins_ = collapse_margins;
...@@ -293,6 +309,15 @@ FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) { ...@@ -293,6 +309,15 @@ FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) {
return *this; return *this;
} }
FlexLayout& FlexLayout::SetIgnoreDefaultMainAxisMargins(
bool ignore_default_main_axis_margins) {
if (ignore_default_main_axis_margins_ != ignore_default_main_axis_margins) {
ignore_default_main_axis_margins_ = ignore_default_main_axis_margins;
InvalidateLayout();
}
return *this;
}
FlexLayout& FlexLayout::SetMinimumCrossAxisSize(int size) { FlexLayout& FlexLayout::SetMinimumCrossAxisSize(int size) {
if (minimum_cross_axis_size_ != size) { if (minimum_cross_axis_size_ != size) {
minimum_cross_axis_size_ = size; minimum_cross_axis_size_ = size;
...@@ -305,8 +330,15 @@ LayoutManagerBase::ProposedLayout FlexLayout::CalculateProposedLayout( ...@@ -305,8 +330,15 @@ LayoutManagerBase::ProposedLayout FlexLayout::CalculateProposedLayout(
const SizeBounds& size_bounds) const { const SizeBounds& size_bounds) const {
FlexLayoutData data; FlexLayoutData data;
if (include_host_insets_in_layout()) {
// Combining the interior margin and host insets means we only have to set
// the margin value; we'll leave the insets at zero.
data.interior_margin =
Normalize(orientation(), interior_margin() + host_view()->GetInsets());
} else {
data.host_insets = Normalize(orientation(), host_view()->GetInsets()); data.host_insets = Normalize(orientation(), host_view()->GetInsets());
data.interior_margin = Normalize(orientation(), interior_margin()); data.interior_margin = Normalize(orientation(), interior_margin());
}
NormalizedSizeBounds bounds = Normalize(orientation(), size_bounds); NormalizedSizeBounds bounds = Normalize(orientation(), size_bounds);
bounds.Inset(data.host_insets); bounds.Inset(data.host_insets);
if (bounds.cross() && *bounds.cross() < minimum_cross_axis_size()) if (bounds.cross() && *bounds.cross() < minimum_cross_axis_size())
...@@ -366,7 +398,8 @@ void FlexLayout::InitializeChildData( ...@@ -366,7 +398,8 @@ void FlexLayout::InitializeChildData(
flex_child.margins = flex_child.margins =
Normalize(orientation(), Normalize(orientation(),
GetViewProperty(child, layout_defaults_, views::kMarginsKey)); GetViewProperty(child, layout_defaults_, views::kMarginsKey,
&flex_child.using_default_margins));
flex_child.internal_padding = Normalize( flex_child.internal_padding = Normalize(
orientation(), orientation(),
GetViewProperty(child, layout_defaults_, views::kInternalPaddingKey)); GetViewProperty(child, layout_defaults_, views::kInternalPaddingKey));
...@@ -443,6 +476,20 @@ void FlexLayout::CalculateChildBounds(const SizeBounds& size_bounds, ...@@ -443,6 +476,20 @@ void FlexLayout::CalculateChildBounds(const SizeBounds& size_bounds,
} }
} }
Inset1D FlexLayout::GetCrossAxisMargins(const FlexLayoutData& layout,
size_t child_index) const {
const FlexChildData& child_data = layout.child_data[child_index];
const int leading_margin =
CalculateMargin(layout.interior_margin.cross_leading(),
child_data.margins.cross_leading(),
child_data.internal_padding.cross_leading());
const int trailing_margin =
CalculateMargin(layout.interior_margin.cross_trailing(),
child_data.margins.cross_trailing(),
child_data.internal_padding.cross_trailing());
return Inset1D(leading_margin, trailing_margin);
}
int FlexLayout::CalculateMargin(int margin1, int FlexLayout::CalculateMargin(int margin1,
int margin2, int margin2,
int internal_padding) const { int internal_padding) const {
...@@ -457,37 +504,39 @@ base::Optional<int> FlexLayout::GetAvailableCrossAxisSize( ...@@ -457,37 +504,39 @@ base::Optional<int> FlexLayout::GetAvailableCrossAxisSize(
const NormalizedSizeBounds& bounds) const { const NormalizedSizeBounds& bounds) const {
if (!bounds.cross()) if (!bounds.cross())
return base::nullopt; return base::nullopt;
const Inset1D cross_margins = GetCrossAxisMargins(layout, child_index);
const FlexChildData& child_layout = layout.child_data[child_index]; return std::max(0, *bounds.cross() - cross_margins.size());
const int leading_margin =
CalculateMargin(layout.interior_margin.cross_leading(),
child_layout.margins.cross_leading(),
child_layout.internal_padding.cross_leading());
const int trailing_margin =
CalculateMargin(layout.interior_margin.cross_trailing(),
child_layout.margins.cross_trailing(),
child_layout.internal_padding.cross_trailing());
return std::max(0, *bounds.cross() - (leading_margin + trailing_margin));
} }
int FlexLayout::CalculateChildSpacing( int FlexLayout::CalculateChildSpacing(
const FlexLayoutData& layout, const FlexLayoutData& layout,
base::Optional<size_t> child1_index, base::Optional<size_t> child1_index,
base::Optional<size_t> child2_index) const { base::Optional<size_t> child2_index) const {
const FlexChildData* const child1 =
child1_index ? &layout.child_data[*child1_index] : nullptr;
const FlexChildData* const child2 =
child2_index ? &layout.child_data[*child2_index] : nullptr;
const int child1_trailing =
child1 && (child2 || !ignore_default_main_axis_margins() ||
!child1->using_default_margins)
? child1->margins.main_trailing()
: 0;
const int child2_leading =
child2 && (child1 || !ignore_default_main_axis_margins() ||
!child2->using_default_margins)
? child2->margins.main_leading()
: 0;
const int left_margin = const int left_margin =
child1_index ? layout.child_data[*child1_index].margins.main_trailing() child1 ? child1_trailing : layout.interior_margin.main_leading();
: layout.interior_margin.main_leading();
const int right_margin = const int right_margin =
child2_index ? layout.child_data[*child2_index].margins.main_leading() child2 ? child2_leading : layout.interior_margin.main_trailing();
: layout.interior_margin.main_trailing();
const int left_padding = const int left_padding =
child1_index child1 ? child1->internal_padding.main_trailing() : 0;
? layout.child_data[*child1_index].internal_padding.main_trailing()
: 0;
const int right_padding = const int right_padding =
child2_index child2 ? child2->internal_padding.main_leading() : 0;
? layout.child_data[*child2_index].internal_padding.main_leading()
: 0;
return CalculateMargin(left_margin, right_margin, return CalculateMargin(left_margin, right_margin,
left_padding + right_padding); left_padding + right_padding);
...@@ -521,20 +570,12 @@ void FlexLayout::UpdateLayoutFromChildren( ...@@ -521,20 +570,12 @@ void FlexLayout::UpdateLayoutFromChildren(
continue; continue;
// Update the cross-axis margins and if necessary, the size. // Update the cross-axis margins and if necessary, the size.
Inset1D& cross_spacing = cross_spacings[i]; cross_spacings[i] = GetCrossAxisMargins(*data, i);
cross_spacing.set_leading(
CalculateMargin(data->interior_margin.cross_leading(),
flex_child.margins.cross_leading(),
flex_child.internal_padding.cross_leading()));
cross_spacing.set_trailing(
CalculateMargin(data->interior_margin.cross_trailing(),
flex_child.margins.cross_trailing(),
flex_child.internal_padding.cross_trailing()));
if (!force_cross_size) { if (!force_cross_size) {
const int cross_size = std::min(flex_child.current_size.cross(), const int cross_size = std::min(flex_child.current_size.cross(),
flex_child.preferred_size.cross()); flex_child.preferred_size.cross());
data->total_size.SetToMax(0, cross_spacing.size() + cross_size); data->total_size.SetToMax(0, cross_spacings[i].size() + cross_size);
} }
// Calculate main-axis size and upper-left main axis coordinate. // Calculate main-axis size and upper-left main axis coordinate.
......
...@@ -81,11 +81,14 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -81,11 +81,14 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// .SetCrossAxisAlignment() // .SetCrossAxisAlignment()
// .SetDefaultFlex(...); // .SetDefaultFlex(...);
FlexLayout& SetOrientation(LayoutOrientation orientation); FlexLayout& SetOrientation(LayoutOrientation orientation);
FlexLayout& SetCollapseMargins(bool collapse_margins);
FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment); FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment);
FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment); FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment);
FlexLayout& SetInteriorMargin(const gfx::Insets& interior_margin); FlexLayout& SetInteriorMargin(const gfx::Insets& interior_margin);
FlexLayout& SetMinimumCrossAxisSize(int size); FlexLayout& SetMinimumCrossAxisSize(int size);
FlexLayout& SetCollapseMargins(bool collapse_margins);
FlexLayout& SetIncludeHostInsetsInLayout(bool include_host_insets_in_layout);
FlexLayout& SetIgnoreDefaultMainAxisMargins(
bool ignore_default_main_axis_margins);
LayoutOrientation orientation() const { return orientation_; } LayoutOrientation orientation() const { return orientation_; }
bool collapse_margins() const { return collapse_margins_; } bool collapse_margins() const { return collapse_margins_; }
...@@ -93,6 +96,12 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -93,6 +96,12 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
LayoutAlignment cross_axis_alignment() const { return cross_axis_alignment_; } LayoutAlignment cross_axis_alignment() const { return cross_axis_alignment_; }
const gfx::Insets& interior_margin() const { return interior_margin_; } const gfx::Insets& interior_margin() const { return interior_margin_; }
int minimum_cross_axis_size() const { return minimum_cross_axis_size_; } int minimum_cross_axis_size() const { return minimum_cross_axis_size_; }
bool include_host_insets_in_layout() const {
return include_host_insets_in_layout_;
}
bool ignore_default_main_axis_margins() const {
return ignore_default_main_axis_margins_;
}
// Moves and uses |value| as the default value for layout property |key|. // Moves and uses |value| as the default value for layout property |key|.
template <class T, class U> template <class T, class U>
...@@ -135,6 +144,11 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -135,6 +144,11 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// See FlexSpecification::order(). // See FlexSpecification::order().
using FlexOrderToViewIndexMap = std::map<int, std::vector<size_t>>; using FlexOrderToViewIndexMap = std::map<int, std::vector<size_t>>;
// Returns the combined margins across the cross axis of the host view, for a
// particular child view.
Inset1D GetCrossAxisMargins(const FlexLayoutData& layout,
size_t child_index) const;
// Calculates a margin between two child views based on each's margin and any // Calculates a margin between two child views based on each's margin and any
// internal padding present in one or both elements. Uses properties of the // internal padding present in one or both elements. Uses properties of the
// layout, like whether adjacent margins should be collapsed. // layout, like whether adjacent margins should be collapsed.
...@@ -222,6 +236,33 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -222,6 +236,33 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// The minimum cross axis size for the layout. // The minimum cross axis size for the layout.
int minimum_cross_axis_size_ = 0; int minimum_cross_axis_size_ = 0;
// Whether to include host insets in the layout. Use when e.g. the host has an
// empty border and you want to treat that empty space as part of the interior
// margin of the host view.
//
// Most useful in conjunction with |collapse_margins| so child margins can
// overlap with the host's insets.
//
// In the future, we might consider putting this as metadata on the host's
// border - e.g. an EmptyBorder would be included in host insets but a thick
// frame would not be.
bool include_host_insets_in_layout_ = false;
// Whether host |interior_margin| overrides default child margins at the
// leading and trailing edge of the host view.
//
// Example:
// layout->SetIgnoreDefaultMainAxisMargins(true)
// .SetCollapseMargins(true)
// .SetDefault(kMarginsKey, {5, 10})
// .SetInteriorMargin({5, 5});
//
// This produces a margin of 5 DIP on all edges of the host view, with 10 DIP
// between child views. If SetIgnoreDefaultMainAxisMargins(true) was not
// called, the default child margin of 10 would also apply on the leading and
// trailing edge of the host view.
bool ignore_default_main_axis_margins_ = false;
// Default properties for any views that don't have them explicitly set for // Default properties for any views that don't have them explicitly set for
// this layout. // this layout.
PropertyHandler layout_defaults_{this}; PropertyHandler layout_defaults_{this};
......
...@@ -821,6 +821,129 @@ TEST_F(FlexLayoutTest, Layout_HostInsets_Vertical_End) { ...@@ -821,6 +821,129 @@ TEST_F(FlexLayoutTest, Layout_HostInsets_Vertical_End) {
EXPECT_EQ(Rect(6, expected_y, 12, 10), child->bounds()); EXPECT_EQ(Rect(6, expected_y, 12, 10), child->bounds());
} }
// Include Host Insets Tests ---------------------------------------------------
TEST_F(FlexLayoutTest, SetIncludeHostInsetsInLayout_NoChange) {
host_->SetBorder(views::CreateEmptyBorder(2, 2, 2, 2));
layout_->SetOrientation(LayoutOrientation::kVertical);
layout_->SetCollapseMargins(false);
layout_->SetInteriorMargin(kLayoutInsets);
layout_->SetMainAxisAlignment(LayoutAlignment::kStart);
layout_->SetCrossAxisAlignment(LayoutAlignment::kEnd);
layout_->SetDefault(views::kMarginsKey, gfx::Insets(4));
constexpr Size kChildSize(10, 10);
AddChild(kChildSize);
View* const child2 = AddChild(kChildSize);
AddChild(kChildSize);
child2->SetProperty(views::kMarginsKey, gfx::Insets(10));
const Size expected_preferred_size = host_->GetPreferredSize();
host_->SetSize(expected_preferred_size);
const std::vector<Rect> expected_bounds = GetChildBounds();
layout_->SetIncludeHostInsetsInLayout(true);
const Size preferred_size = host_->GetPreferredSize();
EXPECT_EQ(expected_preferred_size, preferred_size);
host_->Layout();
EXPECT_EQ(expected_bounds, GetChildBounds());
}
TEST_F(FlexLayoutTest, SetIncludeHostInsetsInLayout_CollapseIntoInsets) {
host_->SetBorder(views::CreateEmptyBorder(2, 2, 2, 2));
layout_->SetOrientation(LayoutOrientation::kVertical);
layout_->SetCollapseMargins(true);
layout_->SetInteriorMargin(kLayoutInsets);
layout_->SetMainAxisAlignment(LayoutAlignment::kStart);
layout_->SetCrossAxisAlignment(LayoutAlignment::kEnd);
layout_->SetDefault(views::kMarginsKey, gfx::Insets(4));
constexpr Size kChildSize(10, 10);
AddChild(kChildSize);
View* const child2 = AddChild(kChildSize);
AddChild(kChildSize);
child2->SetProperty(views::kMarginsKey, gfx::Insets(15));
layout_->SetIncludeHostInsetsInLayout(true);
const Size preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(40, 76), preferred_size);
host_->SetSize(preferred_size);
const std::vector<Rect> expected{Rect(19, 7, 10, 10), Rect(15, 32, 10, 10),
Rect(19, 57, 10, 10)};
EXPECT_EQ(expected, GetChildBounds());
}
TEST_F(FlexLayoutTest, SetIncludeHostInsetsInLayout_OverlapInsets) {
host_->SetBorder(views::CreateEmptyBorder(4, 5, 5, 5));
layout_->SetOrientation(LayoutOrientation::kHorizontal);
layout_->SetCollapseMargins(true);
layout_->SetInteriorMargin(kLayoutInsets);
layout_->SetMainAxisAlignment(LayoutAlignment::kStart);
layout_->SetCrossAxisAlignment(LayoutAlignment::kStart);
View* const child = AddChild(Size(10, 10));
child->SetProperty(views::kInternalPaddingKey, Insets(10, 10, 10, 10));
layout_->SetIncludeHostInsetsInLayout(true);
const Size preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(15, 12), preferred_size);
host_->SetSize(preferred_size);
EXPECT_EQ(Rect(1, 0, 10, 10), child->bounds());
}
// Default Main Axis Margins Tests ---------------------------------------------
TEST_F(FlexLayoutTest, SetIgnoreDefaultMainAxisMargins_IgnoresDefaultMargins) {
layout_->SetOrientation(LayoutOrientation::kVertical);
layout_->SetCollapseMargins(false);
layout_->SetInteriorMargin(kLayoutInsets);
layout_->SetMainAxisAlignment(LayoutAlignment::kStart);
layout_->SetCrossAxisAlignment(LayoutAlignment::kEnd);
layout_->SetDefault(views::kMarginsKey, gfx::Insets(4));
constexpr Size kChildSize(10, 10);
AddChild(kChildSize);
AddChild(kChildSize);
AddChild(kChildSize);
Size preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(33, 66), preferred_size);
layout_->SetIgnoreDefaultMainAxisMargins(true);
preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(33, 58), preferred_size);
host_->SetSize(preferred_size);
const std::vector<Rect> expected{Rect(10, 5, 10, 10), Rect(10, 23, 10, 10),
Rect(10, 41, 10, 10)};
EXPECT_EQ(expected, GetChildBounds());
}
TEST_F(FlexLayoutTest,
SetIgnoreDefaultMainAxisMargins_IncludesExplicitMargins) {
layout_->SetOrientation(LayoutOrientation::kVertical);
layout_->SetCollapseMargins(true);
layout_->SetInteriorMargin(kLayoutInsets);
layout_->SetMainAxisAlignment(LayoutAlignment::kStart);
layout_->SetCrossAxisAlignment(LayoutAlignment::kStart);
layout_->SetDefault(views::kMarginsKey, gfx::Insets(4));
constexpr Size kChildSize(10, 10);
View* const child1 = AddChild(kChildSize);
AddChild(kChildSize);
View* const child3 = AddChild(kChildSize);
child1->SetProperty(views::kMarginsKey, gfx::Insets(11));
child3->SetProperty(views::kMarginsKey, gfx::Insets(12));
Size preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(34, 76), preferred_size);
layout_->SetIgnoreDefaultMainAxisMargins(true);
preferred_size = host_->GetPreferredSize();
EXPECT_EQ(Size(34, 76), preferred_size);
host_->SetSize(preferred_size);
const std::vector<Rect> expected{Rect(11, 11, 10, 10), Rect(6, 32, 10, 10),
Rect(12, 54, 10, 10)};
EXPECT_EQ(expected, GetChildBounds());
}
// Alignment Tests ------------------------------------------------------------- // Alignment Tests -------------------------------------------------------------
TEST_F(FlexLayoutTest, Layout_CrossStart) { TEST_F(FlexLayoutTest, Layout_CrossStart) {
......
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