1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
6 #define UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
7 
8 #include <list>
9 #include <map>
10 #include <memory>
11 #include <set>
12 #include <string>
13 #include <utility>
14 #include <vector>
15 
16 #include "base/compiler_specific.h"
17 #include "base/macros.h"
18 #include "base/optional.h"
19 #include "ui/base/class_property.h"
20 #include "ui/gfx/geometry/insets.h"
21 #include "ui/views/layout/flex_layout_types.h"
22 #include "ui/views/layout/layout_manager_base.h"
23 #include "ui/views/view_class_properties.h"
24 #include "ui/views/views_export.h"
25 
26 namespace views {
27 
28 class NormalizedSize;
29 class NormalizedSizeBounds;
30 class View;
31 
32 // Provides CSS-like layout for a one-dimensional (vertical or horizontal)
33 // arrangement of child views. Independent alignment can be specified for the
34 // main and cross axes.
35 //
36 // Per-View margins (provided by view property kMarginsKey) specify how much
37 // space to leave around each child view. The |interior_margin| says how much
38 // empty space to leave at the edges of the parent view. If |collapse_margins|
39 // is false, these values are additive; if true, the greater of the two values
40 // is used. The |default_child_margins| provides a fallback for views without
41 // kMarginsKey set.
42 //
43 // collapse_margins = false:
44 //
45 // | interior margin>                      <margin [view]...
46 // |                 <margin [view] margin>
47 //
48 // collapse_margins = true:
49 //
50 // | interior margin>      <margin [view]
51 // |         <margin [view] margin>       ...
52 //
53 // Views can have their own internal padding, using the kInternalPaddingKey
54 // property, which is subtracted from the margin space between child views.
55 //
56 // Calling SetVisible(false) on a child view outside of the FlexLayout will
57 // result in the child view being hidden until SetVisible(true) is called. This
58 // is irrespective of whether the FlexLayout has set the child view to be
59 // visible or not based on, for example, flex rules.
60 //
61 // If you want the host view to maintain control over a child view, you can
62 // exclude it from the layout. Excluded views are completely ignored during
63 // layout and do not have their properties modified.
64 //
65 // FlexSpecification objects determine how child views are sized. You can set
66 // individual flex rules for each child view, or a default for any child views
67 // without individual flex rules set. If you don't set anything, each view will
68 // take up its preferred size in the layout.
69 //
70 // The core function of this class is contained in
71 // GetPreferredSize(maximum_size) and Layout(). In both cases, a layout will
72 // be cached and typically not recalculated as long as none of the layout's
73 // properties or the preferred size or visibility of any of its children has
74 // changed.
75 class VIEWS_EXPORT FlexLayout : public LayoutManagerBase {
76  public:
77   FlexLayout();
78   ~FlexLayout() override;
79 
80   // Note: setters provide a Builder-style interface, so you can type:
81   // layout.SetMainAxisAlignment()
82   //       .SetCrossAxisAlignment()
83   //       .SetDefaultFlex(...);
84   // Note that cross-axis alignment can be overridden per-child using:
85   //   child->SetProperty(kCrossAxisAlignmentKey, <value>);
86   FlexLayout& SetOrientation(LayoutOrientation orientation);
87   FlexLayout& SetMainAxisAlignment(LayoutAlignment main_axis_alignment);
88   FlexLayout& SetCrossAxisAlignment(LayoutAlignment cross_axis_alignment);
89   FlexLayout& SetInteriorMargin(const gfx::Insets& interior_margin);
90   FlexLayout& SetMinimumCrossAxisSize(int size);
91   FlexLayout& SetCollapseMargins(bool collapse_margins);
92   FlexLayout& SetIncludeHostInsetsInLayout(bool include_host_insets_in_layout);
93   FlexLayout& SetIgnoreDefaultMainAxisMargins(
94       bool ignore_default_main_axis_margins);
95   FlexLayout& SetFlexAllocationOrder(FlexAllocationOrder flex_allocation_order);
96 
orientation()97   LayoutOrientation orientation() const { return orientation_; }
collapse_margins()98   bool collapse_margins() const { return collapse_margins_; }
main_axis_alignment()99   LayoutAlignment main_axis_alignment() const { return main_axis_alignment_; }
cross_axis_alignment()100   LayoutAlignment cross_axis_alignment() const {
101     return *GetDefault(kCrossAxisAlignmentKey);
102   }
interior_margin()103   const gfx::Insets& interior_margin() const { return interior_margin_; }
minimum_cross_axis_size()104   int minimum_cross_axis_size() const { return minimum_cross_axis_size_; }
include_host_insets_in_layout()105   bool include_host_insets_in_layout() const {
106     return include_host_insets_in_layout_;
107   }
ignore_default_main_axis_margins()108   bool ignore_default_main_axis_margins() const {
109     return ignore_default_main_axis_margins_;
110   }
flex_allocation_order()111   FlexAllocationOrder flex_allocation_order() const {
112     return flex_allocation_order_;
113   }
114 
115   // Returns a flex rule that allows flex layouts to be nested with expected
116   // behavior.
117   FlexRule GetDefaultFlexRule() const;
118 
119   // Moves and uses |value| as the default value for layout property |key|.
120   template <class T, class U>
SetDefault(const ui::ClassProperty<T> * key,U && value)121   FlexLayout& SetDefault(const ui::ClassProperty<T>* key, U&& value) {
122     layout_defaults_.SetProperty(key, std::forward<U>(value));
123     return *this;
124   }
125 
126   // Copies and uses |value| as the default value for layout property |key|.
127   template <class T, class U>
SetDefault(const ui::ClassProperty<T> * key,const U & value)128   FlexLayout& SetDefault(const ui::ClassProperty<T>* key, const U& value) {
129     layout_defaults_.SetProperty(key, value);
130     return *this;
131   }
132 
133  protected:
134   // LayoutManagerBase:
135   ProposedLayout CalculateProposedLayout(
136       const SizeBounds& size_bounds) const override;
137 
138  private:
139   struct ChildLayoutParams;
140   class ChildViewSpacing;
141   struct FlexLayoutData;
142 
143   class PropertyHandler : public ui::PropertyHandler {
144    public:
145     explicit PropertyHandler(FlexLayout* layout);
146 
147    protected:
148     // ui::PropertyHandler:
149     void AfterPropertyChange(const void* key, int64_t old_value) override;
150 
151    private:
152     FlexLayout* const layout_;
153   };
154 
155   using ChildIndices = std::list<size_t>;
156 
157   // Maps a flex order (lower = allocated first, and therefore higher priority)
158   // to the indices of child views within that order that can flex.
159   // See FlexSpecification::order().
160   using FlexOrderToViewIndexMap = std::map<int, ChildIndices>;
161 
162   // Alignment used when the main-axis alignment is not specified.
163   static constexpr LayoutAlignment kDefaultMainAxisAlignment =
164       LayoutAlignment::kStart;
165 
166   // Layout used when the cross-axis alignment is not specified.
167   static constexpr LayoutAlignment kDefaultCrossAxisAlignment =
168       LayoutAlignment::kStretch;
169 
170   // Returns the preferred size for a given |rule| and |child| given unbounded
171   // space, with the caveat that for vertical layouts the horizontal axis is
172   // bounded to |available_cross| to factor in height-for-width considerations.
173   // This corresponds to the FlexSpecification "preferred size".
174   NormalizedSize GetPreferredSizeForRule(
175       const FlexRule& rule,
176       const View* child,
177       const SizeBound& available_cross) const;
178 
179   // Returns the size for a given |rule| and |child| with |available| space.
180   NormalizedSize GetCurrentSizeForRule(
181       const FlexRule& rule,
182       const View* child,
183       const NormalizedSizeBounds& available) const;
184 
185   // Returns the combined margins across the cross axis of the host view, for a
186   // particular child view.
187   Inset1D GetCrossAxisMargins(const FlexLayoutData& layout,
188                               size_t child_index) const;
189 
190   // Calculates a margin between two child views based on each's margin,
191   // inter-child spacing, and any internal padding present in one or both
192   // elements. Uses properties of the layout, like whether adjacent margins
193   // should be collapsed.
194   int CalculateMargin(int margin1, int margin2, int internal_padding) const;
195 
196   // Calculates the cross-layout space available to a view based on the
197   // available space and margins.
198   SizeBound GetAvailableCrossAxisSize(const FlexLayoutData& layout,
199                                       size_t child_index,
200                                       const NormalizedSizeBounds& bounds) const;
201 
202   // Calculates the preferred spacing between two child views, or between a
203   // view edge and the first or last visible child views.
204   int CalculateChildSpacing(const FlexLayoutData& layout,
205                             base::Optional<size_t> child1_index,
206                             base::Optional<size_t> child2_index) const;
207 
208   // Calculates the position of each child view and the size of the overall
209   // layout based on tentative visibilities and sizes for each child.
210   void UpdateLayoutFromChildren(const NormalizedSizeBounds& bounds,
211                                 FlexLayoutData& data,
212                                 ChildViewSpacing& child_spacing) const;
213 
214   // Fills out the child entries for |data| and generates some initial size
215   // and visibility data, and stores off information about which views can
216   // expand in |flex_order_to_index|.
217   void InitializeChildData(const NormalizedSizeBounds& bounds,
218                            FlexLayoutData& data,
219                            FlexOrderToViewIndexMap& flex_order_to_index) const;
220 
221   // Caclulates the child bounds (in screen coordinates) for each visible child
222   // in the layout.
223   void CalculateChildBounds(const SizeBounds& size_bounds,
224                             FlexLayoutData& data) const;
225 
226   // Calculates available space along the main axis for non-flex views and
227   // the values in |data.child_data|.
228   void CalculateNonFlexAvailableSpace(const SizeBound& available_space,
229                                       const FlexOrderToViewIndexMap& flex_views,
230                                       const ChildViewSpacing& child_spacing,
231                                       FlexLayoutData& data) const;
232 
233   // Allocates space shortage (when the available space is less than the
234   // preferred size of the layout) across child views that can flex.
235   //
236   // Updates are made to |data| and |child_spacing|, and views that can still
237   // expand above their preferred size are added to |expandable_views| for later
238   // processing by AllocateFlexExcess().
239   void AllocateFlexShortage(const NormalizedSizeBounds& bounds,
240                             const FlexOrderToViewIndexMap& order_to_index,
241                             FlexLayoutData& data,
242                             ChildViewSpacing& child_spacing,
243                             FlexOrderToViewIndexMap& expandable_views) const;
244 
245   // Allocates space above each child view's preferred size, based on remaining/
246   // excess space in the layout.
247   void AllocateFlexExcess(const NormalizedSizeBounds& bounds,
248                           const FlexOrderToViewIndexMap& order_to_index,
249                           FlexLayoutData& data,
250                           ChildViewSpacing& child_spacing) const;
251 
252   // Updates the available space for each flex child in |child_indices| in
253   // |data.child_data| based on |data.total_size|, |bounds|, and the margin data
254   // in |child_spacing|.
255   void CalculateFlexAvailableSpace(const NormalizedSizeBounds& bounds,
256                                    const ChildIndices& child_indices,
257                                    const ChildViewSpacing& child_spacing,
258                                    FlexLayoutData& data) const;
259 
260   // Pre-allocates space associated with zero-weight views at a particular flex
261   // priority |flex_order|. Zero-weight child views are removed from
262   // |child_list| and their entries are updated in |data|. If |expandable_views|
263   // is specified, this is treated as the first pass, and space allocated to
264   // each view is capped at its preferred size; if the view would claim more
265   // space it is added to |expandable_views| (if specified).
266   void AllocateZeroWeightFlex(const NormalizedSizeBounds& bounds,
267                               int flex_order,
268                               ChildIndices& child_list,
269                               FlexLayoutData& data,
270                               ChildViewSpacing& child_spacing,
271                               FlexOrderToViewIndexMap* expandable_views) const;
272 
273   // Tries to allocate all the views in |child_list| in the available |bounds|.
274   // If successful, updates |data| and |expandable_views|. Returns the
275   // difference between the space needed by all of the views in |child_list| and
276   // the space provided by |bounds|.
277   SizeBound TryAllocateAll(const NormalizedSizeBounds& bounds,
278                            int flex_order,
279                            const ChildIndices& child_list,
280                            FlexLayoutData& data,
281                            ChildViewSpacing& child_spacing,
282                            FlexOrderToViewIndexMap& expandable_views) const;
283 
284   // Allocates flex excess |to_allocate| for a list of child views at the same
285   // priority order.
286   //
287   // It will attempt to do the entire allocation in one pass, removing all
288   // elements from |child_list| that it successfully allocates space for, but in
289   // the event a member of |child_list| does not take its full allocation, it
290   // will remove just that child and set aside its smaller size. At least one
291   // child will always be removed and |to_allocate| will be updated with the
292   // remaining space in the layout.
293   //
294   // This method should be called repeatedly until |child_list| is empty.
295   void AllocateFlexExcessAtOrder(const NormalizedSizeBounds& bounds,
296                                  SizeBound& to_allocate,
297                                  ChildIndices& child_list,
298                                  FlexLayoutData& data,
299                                  ChildViewSpacing& child_spacing) const;
300 
301   // Allocates flex shortage for a list of child views at priority |order|.
302   //
303   // It will attempt to allocate the entire |child_list| in one pass, removing
304   // all elements that it successfully allocates space for, but in the event one
305   // or more members of |child_list| do not take their full allocation, those
306   // views will be allocated and removed from the list, and this method should
307   // be called again on the new, smaller list. At least one child is guaranteed
308   // to be allocated and removed each invocation.
309   //
310   // This method should be called repeatedly until |child_list| is empty.
311   void AllocateFlexShortageAtOrder(const NormalizedSizeBounds& bounds,
312                                    SizeBound deficit,
313                                    ChildIndices& child_list,
314                                    FlexLayoutData& data,
315                                    ChildViewSpacing& child_spacing) const;
316 
317   // Returns the total weight for all children listed in |child_indices|.
318   static int CalculateFlexTotal(const FlexLayoutData& data,
319                                 const ChildIndices& child_indices);
320 
321   // Gets the default value for a particular layout property, which will be used
322   // if the property is not set on a child view being laid out (e.g.
323   // kMarginsKey).
324   template <class T>
GetDefault(const ui::ClassProperty<T * > * key)325   T* GetDefault(const ui::ClassProperty<T*>* key) const {
326     return layout_defaults_.GetProperty(key);
327   }
328 
329   static gfx::Size DefaultFlexRuleImpl(const FlexLayout* flex_layout,
330                                        const View* view,
331                                        const SizeBounds& size_bounds);
332 
333   LayoutOrientation orientation_ = LayoutOrientation::kHorizontal;
334 
335   // Adjacent view margins should be collapsed.
336   bool collapse_margins_ = false;
337 
338   // Spacing between child views and host view border.
339   gfx::Insets interior_margin_;
340 
341   // The alignment of children in the main axis. This is start by default.
342   LayoutAlignment main_axis_alignment_ = kDefaultMainAxisAlignment;
343 
344   // The minimum cross axis size for the layout.
345   int minimum_cross_axis_size_ = 0;
346 
347   // Whether to include host insets in the layout. Use when e.g. the host has an
348   // empty border and you want to treat that empty space as part of the interior
349   // margin of the host view.
350   //
351   // Most useful in conjunction with |collapse_margins| so child margins can
352   // overlap with the host's insets.
353   //
354   // In the future, we might consider putting this as metadata on the host's
355   // border - e.g. an EmptyBorder would be included in host insets but a thick
356   // frame would not be.
357   bool include_host_insets_in_layout_ = false;
358 
359   // Whether host |interior_margin| overrides default child margins at the
360   // leading and trailing edge of the host view.
361   //
362   // Example:
363   // layout->SetIgnoreDefaultMainAxisMargins(true)
364   //        .SetCollapseMargins(true)
365   //        .SetDefault(kMarginsKey, {5, 10})
366   //        .SetInteriorMargin({5, 5});
367   //
368   // This produces a margin of 5 DIP on all edges of the host view, with 10 DIP
369   // between child views. If SetIgnoreDefaultMainAxisMargins(true) was not
370   // called, the default child margin of 10 would also apply on the leading and
371   // trailing edge of the host view.
372   bool ignore_default_main_axis_margins_ = false;
373 
374   // Order in which the host's child views receive their flex allocation.
375   // Setting to reverse is useful when, for example, you want views to drop out
376   // left-to-right when there's insufficient space to display them all instead
377   // of right-to-left.
378   FlexAllocationOrder flex_allocation_order_ = FlexAllocationOrder::kNormal;
379 
380   // Default properties for any views that don't have them explicitly set for
381   // this layout.
382   PropertyHandler layout_defaults_{this};
383 
384   DISALLOW_COPY_AND_ASSIGN(FlexLayout);
385 };
386 
387 }  // namespace views
388 
389 #endif  // UI_VIEWS_LAYOUT_FLEX_LAYOUT_H_
390