blob: a68e3d399de1cd86d4ffea0b0391849e77291100 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
#define UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/optional.h"
#include "ui/base/class_property.h"
#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 {
class NormalizedSize;
class NormalizedSizeBounds;
class View;
// Provides CSS-like layout for a one-dimensional (vertical or horizontal)
// arrangement of child views. Independent alignment can be specified for the
// main and cross axes.
//
// Per-View margins (provided by view property kMarginsKey) specify how much
// space to leave around each child view. The |interior_margin| says how much
// empty space to leave at the edges of the parent view. If |collapse_margins|
// is false, these values are additive; if true, the greater of the two values
// is used. The |default_child_margins| provides a fallback for views without
// kMarginsKey set.
//
// collapse_margins = false:
//
// | interior margin> <margin [view]...
// | <margin [view] margin>
//
// collapse_margins = true:
//
// | interior margin> <margin [view]
// | <margin [view] margin> ...
//
// Views can have their own internal padding, using the kInternalPaddingKey
// property, which is subtracted from the margin space between child views.
//
// Calling SetVisible(false) on a child view outside of the FlexLayout will
// result in the child view being hidden until SetVisible(true) is called. This
// is irrespective of whether the FlexLayout has set the child view to be
// visible or not based on, for example, flex rules.
//
// If you want the host view to maintain control over a child view, you can
// exclude it from the layout. Excluded views are completely ignored during
// layout and do not have their properties modified.
//
// FlexSpecification objects determine how child views are sized. You can set
// individual flex rules for each child view, or a default for any child views
// without individual flex rules set. If you don't set anything, each view will
// take up its preferred size in the layout.
//
// The core function of this class is contained in
// GetPreferredSize(maximum_size) and Layout(). In both cases, a layout will
// be cached and typically not recalculated as long as none of the layout's
// properties or the preferred size or visibility of any of its children has
// changed.
class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
public:
FlexLayout();
~FlexLayout() override;
// Note: setters provide a Builder-style interface, so you can type:
// 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);
FlexLayout& SetInteriorMargin(const gfx::Insets& interior_margin);
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);
FlexLayout& SetFlexAllocationOrder(FlexAllocationOrder flex_allocation_order);
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 *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 {
return include_host_insets_in_layout_;
}
bool ignore_default_main_axis_margins() const {
return ignore_default_main_axis_margins_;
}
FlexAllocationOrder flex_allocation_order() const {
return flex_allocation_order_;
}
// Returns a flex rule that allows flex layouts to be nested with expected
// behavior.
FlexRule GetDefaultFlexRule() const;
// Moves and uses |value| as the default value for layout property |key|.
template <class T, class U>
FlexLayout& SetDefault(const ui::ClassProperty<T>* key, U&& value) {
layout_defaults_.SetProperty(key, std::forward<U>(value));
return *this;
}
// Copies and uses |value| as the default value for layout property |key|.
template <class T, class U>
FlexLayout& SetDefault(const ui::ClassProperty<T>* key, const U& value) {
layout_defaults_.SetProperty(key, value);
return *this;
}
protected:
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override;
private:
struct ChildLayoutParams;
class ChildViewSpacing;
struct FlexLayoutData;
class PropertyHandler : public ui::PropertyHandler {
public:
explicit PropertyHandler(FlexLayout* layout);
protected:
// ui::PropertyHandler:
void AfterPropertyChange(const void* key, int64_t old_value) override;
private:
FlexLayout* const layout_;
};
using ChildIndices = std::list<size_t>;
// Maps a flex order (lower = allocated first, and therefore higher priority)
// to the indices of child views within that order that can flex.
// 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.
// This corresponds to the FlexSpecification "preferred size".
NormalizedSize GetPreferredSizeForRule(
const FlexRule& rule,
const View* child,
const SizeBound& available_cross) const;
// Returns the size for a given |rule| and |child| with |available| space.
NormalizedSize GetCurrentSizeForRule(
const FlexRule& rule,
const View* child,
const NormalizedSizeBounds& available) const;
// 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,
// inter-child spacing, and any internal padding present in one or both
// elements. Uses properties of the layout, like whether adjacent margins
// should be collapsed.
int CalculateMargin(int margin1, int margin2, int internal_padding) const;
// Calculates the cross-layout space available to a view based on the
// available space and margins.
SizeBound GetAvailableCrossAxisSize(const FlexLayoutData& layout,
size_t child_index,
const NormalizedSizeBounds& bounds) const;
// Calculates the preferred spacing between two child views, or between a
// view edge and the first or last visible child views.
int CalculateChildSpacing(const FlexLayoutData& layout,
base::Optional<size_t> child1_index,
base::Optional<size_t> child2_index) const;
// Calculates the position of each child view and the size of the overall
// layout based on tentative visibilities and sizes for each child.
void UpdateLayoutFromChildren(const NormalizedSizeBounds& bounds,
FlexLayoutData& data,
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(const SizeBound& available_space,
const FlexOrderToViewIndexMap& flex_views,
const ChildViewSpacing& child_spacing,
FlexLayoutData& data) const;
// Allocates space shortage (when the available space is less than the
// 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, updates |data| and |expandable_views|. Returns the
// difference between the space needed by all of the views in |child_list| and
// the space provided by |bounds|.
SizeBound TryAllocateAll(const NormalizedSizeBounds& bounds,
int flex_order,
const ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing,
FlexOrderToViewIndexMap& expandable_views) 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,
SizeBound& to_allocate,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const;
// Allocates flex shortage for a list of child views at priority |order|.
//
// It will attempt to allocate the entire |child_list| in one pass, removing
// all elements that it successfully allocates space for, but in the event one
// or more members of |child_list| do not take their full allocation, those
// 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.
//
// This method should be called repeatedly until |child_list| is empty.
void AllocateFlexShortageAtOrder(const NormalizedSizeBounds& bounds,
SizeBound deficit,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) 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
// 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 {
return layout_defaults_.GetProperty(key);
}
static gfx::Size DefaultFlexRuleImpl(const FlexLayout* flex_layout,
const View* view,
const SizeBounds& size_bounds);
LayoutOrientation orientation_ = LayoutOrientation::kHorizontal;
// Adjacent view margins should be collapsed.
bool collapse_margins_ = false;
// Spacing between child views and host view border.
gfx::Insets interior_margin_;
// The alignment of children in the main axis. This is start by default.
LayoutAlignment main_axis_alignment_ = kDefaultMainAxisAlignment;
// The minimum cross axis size for the layout.
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;
// Order in which the host's child views receive their flex allocation.
// Setting to reverse is useful when, for example, you want views to drop out
// left-to-right when there's insufficient space to display them all instead
// of right-to-left.
FlexAllocationOrder flex_allocation_order_ = FlexAllocationOrder::kNormal;
// Default properties for any views that don't have them explicitly set for
// this layout.
PropertyHandler layout_defaults_{this};
DISALLOW_COPY_AND_ASSIGN(FlexLayout);
};
} // namespace views
#endif // UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_