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 @@ ...@@ -18,6 +18,7 @@
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h" #include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "ui/base/class_property.h"
#include "ui/events/event_target.h" #include "ui/events/event_target.h"
#include "ui/events/event_target_iterator.h" #include "ui/events/event_target_iterator.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
...@@ -92,6 +93,10 @@ T MaybeReverse(const T& list, FlexAllocationOrder order) { ...@@ -92,6 +93,10 @@ T MaybeReverse(const T& list, FlexAllocationOrder order) {
// Private implementation ------------------------------------------------------ // 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. // Calculates and maintains 1D spacing between a sequence of child views.
class FlexLayout::ChildViewSpacing { class FlexLayout::ChildViewSpacing {
public: public:
...@@ -303,7 +308,11 @@ void FlexLayout::PropertyHandler::AfterPropertyChange(const void* key, ...@@ -303,7 +308,11 @@ void FlexLayout::PropertyHandler::AfterPropertyChange(const void* key,
// FlexLayout // FlexLayout
// ------------------------------------------------------------------- // -------------------------------------------------------------------
FlexLayout::FlexLayout() = default; FlexLayout::FlexLayout() {
// Ensure this property is always set and is never null.
SetDefault(kCrossAxisAlignmentKey, kDefaultCrossAxisAlignment);
}
FlexLayout::~FlexLayout() = default; FlexLayout::~FlexLayout() = default;
FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) { FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) {
...@@ -344,11 +353,7 @@ FlexLayout& FlexLayout::SetMainAxisAlignment( ...@@ -344,11 +353,7 @@ FlexLayout& FlexLayout::SetMainAxisAlignment(
FlexLayout& FlexLayout::SetCrossAxisAlignment( FlexLayout& FlexLayout::SetCrossAxisAlignment(
LayoutAlignment cross_axis_alignment) { LayoutAlignment cross_axis_alignment) {
if (cross_axis_alignment_ != cross_axis_alignment) { return SetDefault(kCrossAxisAlignmentKey, cross_axis_alignment);
cross_axis_alignment_ = cross_axis_alignment;
InvalidateHost(true);
}
return *this;
} }
FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) { FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) {
...@@ -485,7 +490,9 @@ NormalizedSize FlexLayout::GetPreferredSizeForRule( ...@@ -485,7 +490,9 @@ NormalizedSize FlexLayout::GetPreferredSizeForRule(
// vertical layouts. (We don't do this in horizontal layouts for aesthetic // vertical layouts. (We don't do this in horizontal layouts for aesthetic
// reasons.) // reasons.)
if (orientation() == LayoutOrientation::kVertical) { 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; return stretch_size;
size.set_main(std::max(size.main(), stretch_size.main())); size.set_main(std::max(size.main(), stretch_size.main()));
} }
...@@ -784,7 +791,10 @@ void FlexLayout::UpdateLayoutFromChildren( ...@@ -784,7 +791,10 @@ void FlexLayout::UpdateLayoutFromChildren(
const int starting_cross_size = std::min(flex_child.current_size.cross(), const int starting_cross_size = std::min(flex_child.current_size.cross(),
flex_child.preferred_size.cross()); flex_child.preferred_size.cross());
flex_child.actual_bounds.set_size_cross(starting_cross_size); 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]); cross_spacings[i]);
} }
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/views/layout/flex_layout_types.h" #include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_manager_base.h" #include "ui/views/layout/layout_manager_base.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/views_export.h" #include "ui/views/views_export.h"
namespace views { namespace views {
...@@ -80,6 +81,8 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -80,6 +81,8 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// layout.SetMainAxisAlignment() // layout.SetMainAxisAlignment()
// .SetCrossAxisAlignment() // .SetCrossAxisAlignment()
// .SetDefaultFlex(...); // .SetDefaultFlex(...);
// Note that cross-axis alignment can be overridden per-child using:
// child->SetProperty(kCrossAxisAlignmentKey, <value>);
FlexLayout& SetOrientation(LayoutOrientation orientation); FlexLayout& SetOrientation(LayoutOrientation orientation);
FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment); FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment);
FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment); FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment);
...@@ -94,7 +97,9 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -94,7 +97,9 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
LayoutOrientation orientation() const { return orientation_; } LayoutOrientation orientation() const { return orientation_; }
bool collapse_margins() const { return collapse_margins_; } bool collapse_margins() const { return collapse_margins_; }
LayoutAlignment main_axis_alignment() const { return main_axis_alignment_; } 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_; } 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 { bool include_host_insets_in_layout() const {
...@@ -154,6 +159,14 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -154,6 +159,14 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
// See FlexSpecification::order(). // See FlexSpecification::order().
using FlexOrderToViewIndexMap = std::map<int, ChildIndices>; 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 // 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
// bounded to |available_cross| to factor in height-for-width considerations. // bounded to |available_cross| to factor in height-for-width considerations.
...@@ -309,19 +322,10 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -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. // if the property is not set on a child view being laid out (e.g.
// kMarginsKey). // kMarginsKey).
template <class T> template <class T>
T* GetDefault(const ui::ClassProperty<T>* key) const { T* GetDefault(const ui::ClassProperty<T*>* key) const {
return layout_defaults_.GetProperty(key); 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, static gfx::Size DefaultFlexRuleImpl(const FlexLayout* flex_layout,
const View* view, const View* view,
const SizeBounds& size_bounds); const SizeBounds& size_bounds);
...@@ -335,10 +339,7 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase { ...@@ -335,10 +339,7 @@ class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
gfx::Insets interior_margin_; gfx::Insets interior_margin_;
// The alignment of children in the main axis. This is start by default. // The alignment of children in the main axis. This is start by default.
LayoutAlignment main_axis_alignment_ = LayoutAlignment::kStart; LayoutAlignment main_axis_alignment_ = kDefaultMainAxisAlignment;
// The alignment of children in the cross axis. This is stretch by default.
LayoutAlignment cross_axis_alignment_ = LayoutAlignment::kStretch;
// 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;
......
...@@ -2865,6 +2865,148 @@ TEST_F(FlexLayoutTest, Advanced_PreferredSizeZero_AllOrNothing) { ...@@ -2865,6 +2865,148 @@ TEST_F(FlexLayoutTest, Advanced_PreferredSizeZero_AllOrNothing) {
EXPECT_EQ(expected, GetChildBounds()); 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 -------------------------------------------------------- // Cross-axis Fit Tests --------------------------------------------------------
// Tests for cross-axis alignment that checks three different conditions: // Tests for cross-axis alignment that checks three different conditions:
......
...@@ -25,6 +25,7 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, ...@@ -25,6 +25,7 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*) views::HighlightPathGenerator*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*) DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::LayoutAlignment*)
namespace views { namespace views {
...@@ -38,5 +39,8 @@ DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(views::HighlightPathGenerator, ...@@ -38,5 +39,8 @@ DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(views::HighlightPathGenerator,
kHighlightPathGeneratorKey, kHighlightPathGeneratorKey,
nullptr) nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(FlexSpecification, kFlexBehaviorKey, nullptr) DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(FlexSpecification, kFlexBehaviorKey, nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(LayoutAlignment,
kCrossAxisAlignmentKey,
nullptr)
} // namespace views } // namespace views
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define UI_VIEWS_VIEW_CLASS_PROPERTIES_H_ #define UI_VIEWS_VIEW_CLASS_PROPERTIES_H_
#include "ui/base/class_property.h" #include "ui/base/class_property.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/views_export.h" #include "ui/views/views_export.h"
namespace gfx { namespace gfx {
...@@ -54,6 +55,9 @@ VIEWS_EXPORT extern const ui::ClassProperty<HighlightPathGenerator*>* const ...@@ -54,6 +55,9 @@ VIEWS_EXPORT extern const ui::ClassProperty<HighlightPathGenerator*>* const
VIEWS_EXPORT extern const ui::ClassProperty<FlexSpecification*>* const VIEWS_EXPORT extern const ui::ClassProperty<FlexSpecification*>* const
kFlexBehaviorKey; kFlexBehaviorKey;
VIEWS_EXPORT extern const ui::ClassProperty<LayoutAlignment*>* const
kCrossAxisAlignmentKey;
} // namespace views } // namespace views
// Declaring the template specialization here to make sure that the // Declaring the template specialization here to make sure that the
...@@ -67,6 +71,7 @@ DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, ...@@ -67,6 +71,7 @@ DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*) views::HighlightPathGenerator*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*) 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) DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, bool)
#endif // UI_VIEWS_VIEW_CLASS_PROPERTIES_H_ #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