// 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 #include #include #include #include #include #include #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> // // collapse_margins = true: // // | interior 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, ); 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 FlexLayout& SetDefault(const ui::ClassProperty* key, U&& value) { layout_defaults_.SetProperty(key, std::forward(value)); return *this; } // Copies and uses |value| as the default value for layout property |key|. template FlexLayout& SetDefault(const ui::ClassProperty* 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; // 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; // 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 child1_index, base::Optional 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 T* GetDefault(const ui::ClassProperty* 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_