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

Support per-view cross-axis layout.

This has been a feature request from a number of people for a long time
and was fairly simple to implement.

Change-Id: I7d25a563f654b3fd07f1d4c15cd274dcac65236b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2430766
Commit-Queue: Dana Fried <dfried@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811189}
parent b011b296
......@@ -18,6 +18,7 @@
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "ui/base/class_property.h"
#include "ui/events/event_target.h"
#include "ui/events/event_target_iterator.h"
#include "ui/gfx/geometry/rect.h"
......@@ -92,6 +93,10 @@ T MaybeReverse(const T& list, FlexAllocationOrder order) {
// Private implementation ------------------------------------------------------
// These definitions are required due to the C++ spec.
constexpr LayoutAlignment FlexLayout::kDefaultMainAxisAlignment;
constexpr LayoutAlignment FlexLayout::kDefaultCrossAxisAlignment;
// Calculates and maintains 1D spacing between a sequence of child views.
class FlexLayout::ChildViewSpacing {
public:
......@@ -303,7 +308,11 @@ void FlexLayout::PropertyHandler::AfterPropertyChange(const void* key,
// FlexLayout
// -------------------------------------------------------------------
FlexLayout::FlexLayout() = default;
FlexLayout::FlexLayout() {
// Ensure this property is always set and is never null.
SetDefault(kCrossAxisAlignmentKey, kDefaultCrossAxisAlignment);
}
FlexLayout::~FlexLayout() = default;
FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) {
......@@ -344,11 +353,7 @@ FlexLayout& FlexLayout::SetMainAxisAlignment(
FlexLayout& FlexLayout::SetCrossAxisAlignment(
LayoutAlignment cross_axis_alignment) {
if (cross_axis_alignment_ != cross_axis_alignment) {
cross_axis_alignment_ = cross_axis_alignment;
InvalidateHost(true);
}
return *this;
return SetDefault(kCrossAxisAlignmentKey, cross_axis_alignment);
}
FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) {
......@@ -485,7 +490,9 @@ NormalizedSize FlexLayout::GetPreferredSizeForRule(
// vertical layouts. (We don't do this in horizontal layouts for aesthetic
// reasons.)
if (orientation() == LayoutOrientation::kVertical) {
if (cross_axis_alignment() == LayoutAlignment::kStretch)
const LayoutAlignment cross_align =
GetViewProperty(child, layout_defaults_, kCrossAxisAlignmentKey);
if (cross_align == LayoutAlignment::kStretch)
return stretch_size;
size.set_main(std::max(size.main(), stretch_size.main()));
}
......@@ -784,7 +791,10 @@ void FlexLayout::UpdateLayoutFromChildren(
const int starting_cross_size = std::min(flex_child.current_size.cross(),
flex_child.preferred_size.cross());
flex_child.actual_bounds.set_size_cross(starting_cross_size);
flex_child.actual_bounds.AlignCross(cross_span, cross_axis_alignment(),
const LayoutAlignment cross_align =
GetViewProperty(data.layout.child_layouts[i].child_view,
layout_defaults_, kCrossAxisAlignmentKey);
flex_child.actual_bounds.AlignCross(cross_span, cross_align,
cross_spacings[i]);
}
}
......
......@@ -20,6 +20,7 @@
#include "ui/gfx/geometry/insets.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_manager_base.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/views_export.h"
namespace views {
......@@ -80,6 +81,8 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// layout.SetMainAxisAlignment()
// .SetCrossAxisAlignment()
// .SetDefaultFlex(...);
// Note that cross-axis alignment can be overridden per-child using:
// child->SetProperty(kCrossAxisAlignmentKey, <value>);
FlexLayout& SetOrientation(LayoutOrientation orientation);
FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment);
FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment);
......@@ -94,7 +97,9 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
LayoutOrientation orientation() const { return orientation_; }
bool collapse_margins() const { return collapse_margins_; }
LayoutAlignment main_axis_alignment() const { return main_axis_alignment_; }
LayoutAlignment cross_axis_alignment() const { return cross_axis_alignment_; }
LayoutAlignment cross_axis_alignment() const {
return *GetDefault(kCrossAxisAlignmentKey);
}
const gfx::Insets& interior_margin() const { return interior_margin_; }
int minimum_cross_axis_size() const { return minimum_cross_axis_size_; }
bool include_host_insets_in_layout() const {
......@@ -154,6 +159,14 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// See FlexSpecification::order().
using FlexOrderToViewIndexMap = std::map<int, ChildIndices>;
// Alignment used when the main-axis alignment is not specified.
static constexpr LayoutAlignment kDefaultMainAxisAlignment =
LayoutAlignment::kStart;
// Layout used when the cross-axis alignment is not specified.
static constexpr LayoutAlignment kDefaultCrossAxisAlignment =
LayoutAlignment::kStretch;
// Returns the preferred size for a given |rule| and |child| given unbounded
// space, with the caveat that for vertical layouts the horizontal axis is
// bounded to |available_cross| to factor in height-for-width considerations.
......@@ -309,19 +322,10 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// if the property is not set on a child view being laid out (e.g.
// kMarginsKey).
template <class T>
T* GetDefault(const ui::ClassProperty<T>* key) const {
T* GetDefault(const ui::ClassProperty<T*>* key) const {
return layout_defaults_.GetProperty(key);
}
// Clears 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.
// kMarginsKey).
template <class T>
FlexLayout& ClearDefault(const ui::ClassProperty<T>* key) {
layout_defaults_.ClearProperty(key);
return *this;
}
static gfx::Size DefaultFlexRuleImpl(const FlexLayout* flex_layout,
const View* view,
const SizeBounds& size_bounds);
......@@ -335,10 +339,7 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
gfx::Insets interior_margin_;
// The alignment of children in the main axis. This is start by default.
LayoutAlignment main_axis_alignment_ = LayoutAlignment::kStart;
// The alignment of children in the cross axis. This is stretch by default.
LayoutAlignment cross_axis_alignment_ = LayoutAlignment::kStretch;
LayoutAlignment main_axis_alignment_ = kDefaultMainAxisAlignment;
// The minimum cross axis size for the layout.
int minimum_cross_axis_size_ = 0;
......
......@@ -2865,6 +2865,148 @@ TEST_F(FlexLayoutTest, Advanced_PreferredSizeZero_AllOrNothing) {
EXPECT_EQ(expected, GetChildBounds());
}
// Individual cross-axis alignment test ----------------------------------------
TEST_F(FlexLayoutTest, IndividualCrossAxisAlignmentInHorizontalLayoutTest) {
layout_->SetOrientation(LayoutOrientation::kHorizontal)
.SetDefault(kMarginsKey, gfx::Insets(5));
View* const v1 = AddChild(gfx::Size(10, 10));
v1->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kStart);
View* const v2 = AddChild(gfx::Size(10, 10));
v2->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kCenter);
View* const v3 = AddChild(gfx::Size(10, 10));
v3->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kEnd);
View* const v4 = AddChild(gfx::Size(10, 10));
v4->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kStretch);
View* const v5 = AddChild(gfx::Size(10, 10));
// v5 uses default
host_->SizeToPreferredSize();
EXPECT_EQ(5, v1->y());
EXPECT_EQ(10, v1->height());
EXPECT_EQ(5, v2->y());
EXPECT_EQ(10, v2->height());
EXPECT_EQ(5, v3->y());
EXPECT_EQ(10, v3->height());
EXPECT_EQ(5, v4->y());
EXPECT_EQ(10, v4->height());
EXPECT_EQ(5, v5->y());
EXPECT_EQ(10, v5->height());
// Next try a larger view.
host_->SetSize(gfx::Size(100, 30));
EXPECT_EQ(5, v1->y());
EXPECT_EQ(10, v1->height());
EXPECT_EQ(10, v2->y());
EXPECT_EQ(10, v2->height());
EXPECT_EQ(15, v3->y());
EXPECT_EQ(10, v3->height());
EXPECT_EQ(5, v4->y());
EXPECT_EQ(20, v4->height());
EXPECT_EQ(5, v5->y());
EXPECT_EQ(20, v5->height());
// Move to a smaller view.
host_->SetSize(gfx::Size(100, 12));
EXPECT_EQ(5, v1->y());
EXPECT_EQ(10, v1->height());
EXPECT_EQ(1, v2->y());
EXPECT_EQ(10, v2->height());
EXPECT_EQ(-3, v3->y());
EXPECT_EQ(10, v3->height());
EXPECT_EQ(5, v4->y());
EXPECT_EQ(2, v4->height());
EXPECT_EQ(5, v5->y());
EXPECT_EQ(2, v5->height());
// Change default cross-axis alignment.
layout_->SetCrossAxisAlignment(LayoutAlignment::kCenter);
host_->Layout();
// v1-v4 should remain unchanged.
EXPECT_EQ(5, v1->y());
EXPECT_EQ(10, v1->height());
EXPECT_EQ(1, v2->y());
EXPECT_EQ(10, v2->height());
EXPECT_EQ(-3, v3->y());
EXPECT_EQ(10, v3->height());
EXPECT_EQ(5, v4->y());
EXPECT_EQ(2, v4->height());
// Since v5 doesn't have its own alignment set, it should pick up the new
// default.
EXPECT_EQ(1, v5->y());
EXPECT_EQ(10, v5->height());
}
TEST_F(FlexLayoutTest, IndividualCrossAxisAlignmentInVerticalLayoutTest) {
layout_->SetOrientation(LayoutOrientation::kVertical)
.SetDefault(kMarginsKey, gfx::Insets(5));
View* const v1 = AddChild(gfx::Size(10, 10));
v1->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kStart);
View* const v2 = AddChild(gfx::Size(10, 10));
v2->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kCenter);
View* const v3 = AddChild(gfx::Size(10, 10));
v3->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kEnd);
View* const v4 = AddChild(gfx::Size(10, 10));
v4->SetProperty(kCrossAxisAlignmentKey, LayoutAlignment::kStretch);
View* const v5 = AddChild(gfx::Size(10, 10));
// v5 uses default
host_->SizeToPreferredSize();
EXPECT_EQ(5, v1->x());
EXPECT_EQ(10, v1->width());
EXPECT_EQ(5, v2->x());
EXPECT_EQ(10, v2->width());
EXPECT_EQ(5, v3->x());
EXPECT_EQ(10, v3->width());
EXPECT_EQ(5, v4->x());
EXPECT_EQ(10, v4->width());
EXPECT_EQ(5, v5->x());
EXPECT_EQ(10, v5->width());
// Next try a larger view.
host_->SetSize(gfx::Size(30, 100));
EXPECT_EQ(5, v1->x());
EXPECT_EQ(10, v1->width());
EXPECT_EQ(10, v2->x());
EXPECT_EQ(10, v2->width());
EXPECT_EQ(15, v3->x());
EXPECT_EQ(10, v3->width());
EXPECT_EQ(5, v4->x());
EXPECT_EQ(20, v4->width());
EXPECT_EQ(5, v5->x());
EXPECT_EQ(20, v5->width());
// Move to a smaller view.
host_->SetSize(gfx::Size(12, 100));
EXPECT_EQ(5, v1->x());
EXPECT_EQ(10, v1->width());
EXPECT_EQ(1, v2->x());
EXPECT_EQ(10, v2->width());
EXPECT_EQ(-3, v3->x());
EXPECT_EQ(10, v3->width());
EXPECT_EQ(5, v4->x());
EXPECT_EQ(2, v4->width());
EXPECT_EQ(5, v5->x());
EXPECT_EQ(2, v5->width());
// Change default cross-axis alignment.
layout_->SetCrossAxisAlignment(LayoutAlignment::kCenter);
host_->Layout();
// v1-v4 should remain unchanged.
EXPECT_EQ(5, v1->x());
EXPECT_EQ(10, v1->width());
EXPECT_EQ(1, v2->x());
EXPECT_EQ(10, v2->width());
EXPECT_EQ(-3, v3->x());
EXPECT_EQ(10, v3->width());
EXPECT_EQ(5, v4->x());
EXPECT_EQ(2, v4->width());
// Since v5 doesn't have its own alignment set, it should pick up the new
// default.
EXPECT_EQ(1, v5->x());
EXPECT_EQ(10, v5->width());
}
// Cross-axis Fit Tests --------------------------------------------------------
// Tests for cross-axis alignment that checks three different conditions:
......
......@@ -25,6 +25,7 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::LayoutAlignment*)
namespace views {
......@@ -38,5 +39,8 @@ DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(views::HighlightPathGenerator,
kHighlightPathGeneratorKey,
nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(FlexSpecification, kFlexBehaviorKey, nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(LayoutAlignment,
kCrossAxisAlignmentKey,
nullptr)
} // namespace views
......@@ -6,6 +6,7 @@
#define UI_VIEWS_VIEW_CLASS_PROPERTIES_H_
#include "ui/base/class_property.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/views_export.h"
namespace gfx {
......@@ -54,6 +55,9 @@ VIEWS_EXPORT extern const ui::ClassProperty<HighlightPathGenerator*>* const
VIEWS_EXPORT extern const ui::ClassProperty<FlexSpecification*>* const
kFlexBehaviorKey;
VIEWS_EXPORT extern const ui::ClassProperty<LayoutAlignment*>* const
kCrossAxisAlignmentKey;
} // namespace views
// Declaring the template specialization here to make sure that the
......@@ -67,6 +71,7 @@ DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::LayoutAlignment*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, bool)
#endif // UI_VIEWS_VIEW_CLASS_PROPERTIES_H_
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