| // Copyright 2018 The Chromium Authors |
| // 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 <optional> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/raw_ptr.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. |
| // |
| // 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(const FlexLayout&) = delete; |
| FlexLayout& operator=(const FlexLayout&) = delete; |
| |
| ~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; |
| } |
| |
| template <class T> |
| T* GetDefaultForTesting(const ui::ClassProperty<T*>* key) const { |
| return GetDefault(key); |
| } |
| |
| 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: |
| const raw_ptr<FlexLayout> 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, |
| std::optional<size_t> child1_index, |
| std::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; |
| |
| // 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; |
| |
| // Calculate the size of the view under the given |size| and constrain it to |
| // the maximum and minimum space. |
| NormalizedSize ClampSizeToMinAndMax(FlexLayoutData& data, |
| const size_t view_index, |
| SizeBound size) const; |
| |
| // Allocate space for flexible views |
| void AllocateFlexItem(const NormalizedSizeBounds& bounds, |
| const FlexOrderToViewIndexMap& order_to_index, |
| FlexLayoutData& data, |
| ChildViewSpacing& child_spacing, |
| bool skip_zero_preferred_size_view) 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). |
| SizeBound AllocateZeroWeightFlex(const NormalizedSizeBounds& bounds, |
| ChildIndices& child_list, |
| FlexLayoutData& data, |
| ChildViewSpacing& child_spacing) const; |
| |
| // 0 preferred size views have a unique layout algorithm and they are either |
| // visible together. If they are not visible, they are not visible at all, so |
| // we need to delete them if necessary. |
| void FilterZeroSizeChildreIfNeeded(const NormalizedSizeBounds& bounds, |
| SizeBound& to_allocate, |
| ChildIndices& child_list, |
| FlexLayoutData& data, |
| ChildViewSpacing& child_spacing) const; |
| |
| // Combining the actual situation of CSS flexbox and views, we solve the |
| // problem of flexible view allocation. The main thing here is to integrate |
| // https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths $9.2.7 |
| bool ResolveFlexibleLengths(const NormalizedSizeBounds& bounds, |
| SizeBound& remaining_free_space, |
| ChildIndices& child_list, |
| FlexLayoutData& data, |
| ChildViewSpacing& child_spacing) const; |
| |
| // Freeze flexible views according to css flexbox algorithm. But there is a |
| // special scene. If the view visibility changes. We terminate the freeze and |
| // force a restart. |
| bool FreezeViolations(ChildIndices& child_list, |
| SizeBound& remaining_free_space, |
| ChildIndices& freeze_child_list, |
| ChildViewSpacing& child_spacing, |
| FlexLayoutData& data) const; |
| |
| // Algorithms required by views alone. Because views have custom flex rules, |
| // this may lead to unallocated space. Here we allocate these spaces. |
| void AllocateRemainingSpaceIfNeeded( |
| const NormalizedSizeBounds& bounds, |
| const FlexOrderToViewIndexMap& order_to_index, |
| 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}; |
| }; |
| |
| } // namespace views |
| |
| #endif // UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_ |