Commit 123efb3e authored by Dana Fried's avatar Dana Fried Committed by Commit Bot

Better support for height-for-width and custom flex rules.

It improves but does not completely fix support for height-for-width
calculations, allowing views to exceed preferred size in more cases
where a custom flex rule or height-for-width relationship is present,
especially in places where there is a vertical layout with horizontal
stretch alignment.

We have also added a specific regression test case for the bug in
question, which has example code showing how to make the requested case
work with the existing infrastructure.

Bug: 1012119
Change-Id: I88380771aacfea29e750dd5033f1fa11e9eb999a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1940714Reviewed-by: default avatarCollin Baker <collinbaker@chromium.org>
Commit-Queue: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#721236}
parent c810be73
......@@ -402,20 +402,35 @@ void FlexLayout::InitializeChildData(
flex_child.internal_padding = Normalize(
orientation(),
GetViewProperty(child, layout_defaults_, views::kInternalPaddingKey));
flex_child.preferred_size =
Normalize(orientation(), child->GetPreferredSize());
// FlexSpecification defines "preferred size" as the size returned when we
// do not bound the inputs (specifically the main axis). This is different
// from View::GetPreferredSize() which may not take into account e.g. an
// the necessity to alter a view's height in a vertical layout if the width
// is bounded. In the common case this will be equivalent to calling
// GetPreferredSize().
const base::Optional<int> available_cross_size =
GetAvailableCrossAxisSize(*data, view_index, bounds);
// In vertical layouts it's important to consider height-for-width type
// calculations when evaluating the base/preferred size of the child view.
const base::Optional<int> preferred_cross_size =
orientation() == LayoutOrientation::kVertical ? available_cross_size
: base::nullopt;
flex_child.preferred_size = Normalize(
orientation(),
flex_child.flex.rule().Run(
child,
Denormalize(orientation(), {base::nullopt, preferred_cross_size})));
// gfx::Size calculation depends on whether flex is allowed.
if (main_axis_bounded) {
flex_child.available_size = {
0, GetAvailableCrossAxisSize(*data, view_index, bounds)};
flex_child.available_size = {0, available_cross_size};
flex_child.current_size = Normalize(
orientation(),
flex_child.flex.rule().Run(
child, Denormalize(orientation(), flex_child.available_size)));
// We should revisit whether this is a valid assumption for text views
// in vertical layouts.
DCHECK_GE(flex_child.preferred_size.main(),
flex_child.current_size.main())
<< " in " << child->GetClassName();
......
......@@ -61,12 +61,33 @@ int InterpolateSize(MinimumFlexSizeRule minimum_size_rule,
}
}
// A view's minimum size can in some cases be expensive to compute. This
// provides a lazy-eval value that behaves like a smart pointer but is more
// lightweight than base::LazyInstance.
class LazyMinimumSize {
public:
explicit LazyMinimumSize(const View* view) : view_(view) {}
~LazyMinimumSize() = default;
const gfx::Size* operator->() const { return get(); }
const gfx::Size& operator*() const { return *get(); }
const gfx::Size* get() const {
if (!size_)
size_ = view_->GetMinimumSize();
return &size_.value();
}
private:
const View* const view_;
mutable base::Optional<gfx::Size> size_;
};
gfx::Size GetPreferredSize(MinimumFlexSizeRule minimum_size_rule,
MaximumFlexSizeRule maximum_size_rule,
bool adjust_height_for_width,
const views::View* view,
const views::SizeBounds& maximum_size) {
gfx::Size min = view->GetMinimumSize();
const View* view,
const SizeBounds& maximum_size) {
LazyMinimumSize min(view);
gfx::Size preferred = view->GetPreferredSize();
int width, height;
......@@ -76,32 +97,43 @@ gfx::Size GetPreferredSize(MinimumFlexSizeRule minimum_size_rule,
// size; a view can't grow infinitely, so we go with its preferred size.
width = preferred.width();
} else {
width = InterpolateSize(minimum_size_rule, maximum_size_rule, min.width(),
width = InterpolateSize(minimum_size_rule, maximum_size_rule, min->width(),
preferred.width(), *maximum_size.width());
}
int preferred_height = preferred.height();
if (adjust_height_for_width && width < preferred.width()) {
if (adjust_height_for_width) {
// The |adjust_height_for_width| flag is used in vertical layouts where we
// want views to be able to adapt to the horizontal available space by
// growing vertically. We therefore allow the horizontal size to shrink even
// if there's otherwise no flex allowed.
if (maximum_size.width() && maximum_size.width() > 0)
width = std::min(width, *maximum_size.width());
if (width < preferred.width()) {
// Allow views that need to grow vertically when they're compressed
// horizontally to do so.
//
// If we just went with GetHeightForWidth() we would have situations where
// an empty text control wanted no (or very little) height which could cause
// a layout to shrink vertically; we will always try to allocate at least
// the view's reported preferred height.
// an empty text control wanted no (or very little) height which could
// cause a layout to shrink vertically; we will always try to allocate at
// least the view's reported preferred height.
//
// Note that this is an adjustment made for practical considerations, and
// may not be "correct" in some absolute sense. Let's revisit at some point.
// may not be "correct" in some absolute sense. Let's revisit at some
// point.
preferred_height =
std::max(preferred_height, view->GetHeightForWidth(width));
}
}
if (!maximum_size.height()) {
// Not having a maximum size is different from having a large available
// size; a view can't grow infinitely, so we go with its preferred size.
height = preferred_height;
} else {
height = InterpolateSize(minimum_size_rule, maximum_size_rule, min.height(),
height =
InterpolateSize(minimum_size_rule, maximum_size_rule, min->height(),
preferred_height, *maximum_size.height());
}
......
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