1 // Copyright (c) 2012 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_BOX_LAYOUT_H_
6 #define UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
7 
8 #include <map>
9 
10 #include "ui/gfx/geometry/insets.h"
11 #include "ui/views/layout/layout_manager.h"
12 #include "ui/views/view.h"
13 
14 namespace gfx {
15 class Rect;
16 class Size;
17 }  // namespace gfx
18 
19 namespace views {
20 
21 // A Layout manager that arranges child views vertically or horizontally in a
22 // side-by-side fashion with spacing around and between the child views. The
23 // child views are always sized according to their preferred size. If the
24 // host's bounds provide insufficient space, child views will be clamped.
25 // Excess space will not be distributed.
26 class VIEWS_EXPORT BoxLayout : public LayoutManager {
27  public:
28   enum class Orientation {
29     kHorizontal,
30     kVertical,
31   };
32 
33   // This specifies that the start/center/end of the collective child views is
34   // aligned with the start/center/end of the host view. e.g. a horizontal
35   // layout of MainAxisAlignment::kEnd will result in the child views being
36   // right-aligned.
37   enum class MainAxisAlignment {
38     kStart,
39     kCenter,
40     kEnd,
41     // TODO(calamity): Add MAIN_AXIS_ALIGNMENT_JUSTIFY which spreads blank space
42     // in-between the child views.
43   };
44 
45   // This specifies where along the cross axis the children should be laid out.
46   // e.g. a horizontal layout of kEnd will result in the child views being
47   // bottom-aligned.
48   enum class CrossAxisAlignment {
49     // This causes the child view to stretch to fit the host in the cross axis.
50     kStretch,
51     kStart,
52     kCenter,
53     kEnd,
54   };
55 
56   // Use |inside_border_insets| to add additional space between the child
57   // view area and the host view border. |between_child_spacing| controls the
58   // space in between child views. Use view->SetProperty(kMarginsKey,
59   // gfx::Insets(xxx)) to add additional margins on a per-view basis. The
60   // |collapse_margins_spacing| parameter controls whether or not adjacent
61   // spacing/margins are collapsed based on the max of the two values. For the
62   // cross axis, |collapse_margins_spacing| will collapse to the max of
63   // inside_border_xxxxx_spacing and the corresponding margin edge from each
64   // view.
65   //
66   // Given the following views where V = view bounds, M = Margins property,
67   // B = between child spacing, S = inside border spacing and
68   // <space> = added margins for alignment
69   //
70   // MMMMM  MMVVVVMM  MMMM
71   // VVVVM            MMMM
72   // VVVVM            MMMM
73   // VVVVM            VVVV
74   // MMMMM
75   //
76   // With collapse_margins_spacing = false, orientation = kHorizontal,
77   // inside_border_spacing_horizontal = 2, inside_border_spacing_vertical = 2
78   // and between_child_spacing = 1:
79   //
80   // -----------------------
81   // SSSSSSSSSSSSSSSSSSSSSSS
82   // SSSSSSSSSSSSSSSSSSSSSSS
83   // SS    MBMM    MMBMMMMSS
84   // SS    MBMM    MMBMMMMSS
85   // SSMMMMMBMM    MMBMMMMSS
86   // SSVVVVMBMMVVVVMMBVVVVSS
87   // SSVVVVMBMMVVVVMMBVVVVSS
88   // SSVVVVMBMMVVVVMMBVVVVSS
89   // SSMMMMMBMMVVVVMMBVVVVSS
90   // SSSSSSSSSSSSSSSSSSSSSSS
91   // SSSSSSSSSSSSSSSSSSSSSSS
92   // -----------------------
93   //
94   // Same as above except, collapse_margins_spacing = true.
95   //
96   // --------------------
97   // SS          MMMMMMSS
98   // SS          MMMMMMSS
99   // SSMMMMMM    MMMMMMSS
100   // SSVVVVMMVVVVMMVVVVSS
101   // SSVVVVMMVVVVMMVVVVSS
102   // SSVVVVMMVVVVMMVVVVSS
103   // SSSSSSSSSSSSSSSSSSSS
104   // SSSSSSSSSSSSSSSSSSSS
105   // --------------------
106   //
107   explicit BoxLayout(Orientation orientation = Orientation::kHorizontal,
108                      const gfx::Insets& inside_border_insets = gfx::Insets(),
109                      int between_child_spacing = 0,
110                      bool collapse_margins_spacing = false);
111   ~BoxLayout() override;
112 
113   void SetOrientation(Orientation orientation);
114   Orientation GetOrientation() const;
115 
116   // TODO(tluk): These class member setters should likely be calling
117   // LayoutManager::InvalidateLayout() .
set_main_axis_alignment(MainAxisAlignment main_axis_alignment)118   void set_main_axis_alignment(MainAxisAlignment main_axis_alignment) {
119     main_axis_alignment_ = main_axis_alignment;
120   }
main_axis_alignment()121   MainAxisAlignment main_axis_alignment() const { return main_axis_alignment_; }
122 
set_cross_axis_alignment(CrossAxisAlignment cross_axis_alignment)123   void set_cross_axis_alignment(CrossAxisAlignment cross_axis_alignment) {
124     cross_axis_alignment_ = cross_axis_alignment;
125   }
cross_axis_alignment()126   CrossAxisAlignment cross_axis_alignment() const {
127     return cross_axis_alignment_;
128   }
129 
set_inside_border_insets(const gfx::Insets & insets)130   void set_inside_border_insets(const gfx::Insets& insets) {
131     inside_border_insets_ = insets;
132   }
inside_border_insets()133   const gfx::Insets& inside_border_insets() const {
134     return inside_border_insets_;
135   }
136 
set_minimum_cross_axis_size(int size)137   void set_minimum_cross_axis_size(int size) {
138     minimum_cross_axis_size_ = size;
139   }
minimum_cross_axis_size()140   int minimum_cross_axis_size() const { return minimum_cross_axis_size_; }
141 
set_between_child_spacing(int spacing)142   void set_between_child_spacing(int spacing) {
143     between_child_spacing_ = spacing;
144   }
between_child_spacing()145   int between_child_spacing() const { return between_child_spacing_; }
146 
147   void SetCollapseMarginsSpacing(bool collapse_margins_spacing);
148   bool GetCollapseMarginsSpacing() const;
149 
150   // Sets the flex weight for the given |view|. Using the preferred size as
151   // the basis, free space along the main axis is distributed to views in the
152   // ratio of their flex weights. Similarly, if the views will overflow the
153   // parent, space is subtracted in these ratios.
154   // If true is passed in for |use_min_size|, the given view's minimum size
155   // is then obtained from calling View::GetMinimumSize(). This will be the
156   // minimum allowed size for the view along the main axis. False
157   // for |use_min_size| (the default) will allow the |view| to be resized to a
158   // minimum size of 0.
159   //
160   // A flex of 0 means this view is not resized. Flex values must not be
161   // negative.
162   void SetFlexForView(const View* view, int flex, bool use_min_size = false);
163 
164   // Clears the flex for the given |view|, causing it to use the default
165   // flex.
166   void ClearFlexForView(const View* view);
167 
168   // Sets the flex for views to use when none is specified.
169   void SetDefaultFlex(int default_flex);
170   int GetDefaultFlex() const;
171 
172   // Overridden from views::LayoutManager:
173   void Installed(View* host) override;
174   void ViewRemoved(View* host, View* view) override;
175   void Layout(View* host) override;
176   gfx::Size GetPreferredSize(const View* host) const override;
177   int GetPreferredHeightForWidth(const View* host, int width) const override;
178 
179  private:
180   // This struct is used internally to "wrap" a child view in order to obviate
181   // the need for the main layout logic to be fully aware of the per-view
182   // margins when |collapse_margin_spacing_| is false. Since each view is a
183   // rectangle of a certain size, this wrapper, coupled with any margins set
184   // will increase the apparent size of the view along the main axis. All
185   // necessary view size/position methods required for the layout logic add or
186   // subtract the margins where appropriate to ensure the actual visible size of
187   // the view doesn't include the margins. For the cross axis, the margins are
188   // NOT included in the size/position calculations. BoxLayout will adjust the
189   // bounding rectangle of the space used for layout using the maximum margin
190   // for all views along the appropriate edge.
191   // When |collapse_margin_spacing_| is true, this wrapper provides quick access
192   // to the view's margins for use by the layout to collapse adjacent spacing
193   // to the largest of the several values.
194   class ViewWrapper {
195    public:
196     ViewWrapper();
197     ViewWrapper(const BoxLayout* layout, View* view);
198     ~ViewWrapper();
199 
200     int GetHeightForWidth(int width) const;
margins()201     const gfx::Insets& margins() const { return margins_; }
202     gfx::Size GetPreferredSize() const;
203     void SetBoundsRect(const gfx::Rect& bounds);
view()204     View* view() const { return view_; }
205     bool visible() const;
206 
207    private:
208     View* view_ = nullptr;
209     const BoxLayout* layout_ = nullptr;
210     gfx::Insets margins_;
211 
212     DISALLOW_COPY_AND_ASSIGN(ViewWrapper);
213   };
214 
215   struct Flex {
216     int flex_weight;
217     bool use_min_size;
218   };
219 
220   using FlexMap = std::map<const View*, Flex>;
221 
222   // Returns the flex for the specified |view|.
223   int GetFlexForView(const View* view) const;
224 
225   // Returns the minimum size for the specified |view|.
226   int GetMinimumSizeForView(const View* view) const;
227 
228   // Returns the size and position along the main axis of |rect|.
229   int MainAxisSize(const gfx::Rect& rect) const;
230   int MainAxisPosition(const gfx::Rect& rect) const;
231 
232   // Sets the size and position along the main axis of |rect|.
233   void SetMainAxisSize(int size, gfx::Rect* rect) const;
234   void SetMainAxisPosition(int position, gfx::Rect* rect) const;
235 
236   // Returns the size and position along the cross axis of |rect|.
237   int CrossAxisSize(const gfx::Rect& rect) const;
238   int CrossAxisPosition(const gfx::Rect& rect) const;
239 
240   // Sets the size and position along the cross axis of |rect|.
241   void SetCrossAxisSize(int size, gfx::Rect* rect) const;
242   void SetCrossAxisPosition(int size, gfx::Rect* rect) const;
243 
244   // Returns the main axis size for the given view. |child_area_width| is needed
245   // to calculate the height of the view when the orientation is vertical.
246   int MainAxisSizeForView(const ViewWrapper& view, int child_area_width) const;
247 
248   // Returns the |left| or |top| edge of the given inset based on the value of
249   // |orientation_|.
250   int MainAxisLeadingInset(const gfx::Insets& insets) const;
251 
252   // Returns the |right| or |bottom| edge of the given inset based on the value
253   // of |orientation_|.
254   int MainAxisTrailingInset(const gfx::Insets& insets) const;
255 
256   // Returns the left (|x|) or top (|y|) edge of the given rect based on the
257   // value of |orientation_|.
258   int CrossAxisLeadingEdge(const gfx::Rect& rect) const;
259 
260   // Returns the |left| or |top| edge of the given inset based on the value of
261   // |orientation_|.
262   int CrossAxisLeadingInset(const gfx::Insets& insets) const;
263 
264   // Returns the |right| or |bottom| edge of the given inset based on the value
265   // of |orientation_|.
266   int CrossAxisTrailingInset(const gfx::Insets& insets) const;
267 
268   // Returns the main axis margin spacing between the two views which is the max
269   // of the right margin from the |left| view, the left margin of the |right|
270   // view and |between_child_spacing_|.
271   int MainAxisMarginBetweenViews(const ViewWrapper& left,
272                                  const ViewWrapper& right) const;
273 
274   // Returns the outer margin along the main axis as insets.
275   gfx::Insets MainAxisOuterMargin() const;
276 
277   // Returns the maximum margin along the cross axis from all views as insets.
278   gfx::Insets CrossAxisMaxViewMargin() const;
279 
280   // Adjusts the main axis for |rect| by collapsing the left or top margin of
281   // the first view with corresponding side of |inside_border_insets_| and the
282   // right or bottom margin of the last view with the corresponding side of
283   // |inside_border_insets_|.
284   void AdjustMainAxisForMargin(gfx::Rect* rect) const;
285 
286   // Adjust the cross axis for |rect| using the appropriate sides of
287   // |inside_border_insets_|.
288   void AdjustCrossAxisForInsets(gfx::Rect* rect) const;
289 
290   // Returns the cross axis size for the given view.
291   int CrossAxisSizeForView(const ViewWrapper& view) const;
292 
293   // Returns the total margin width for the given view or 0 when
294   // collapse_margins_spacing_ is true.
295   int CrossAxisMarginSizeForView(const ViewWrapper& view) const;
296 
297   // Returns the Top or Left size of the margin for the given view or 0 when
298   // collapse_margins_spacing_ is true.
299   int CrossAxisLeadingMarginForView(const ViewWrapper& view) const;
300 
301   // Adjust the cross axis for |rect| using the given leading and trailing
302   // values.
303   void InsetCrossAxis(gfx::Rect* rect, int leading, int trailing) const;
304 
305   // The preferred size for the dialog given the width of the child area.
306   gfx::Size GetPreferredSizeForChildWidth(const View* host,
307                                           int child_area_width) const;
308 
309   // The amount of space the layout requires in addition to any space for the
310   // child views.
311   gfx::Size NonChildSize(const View* host) const;
312 
313   // The next visible view at or after pos. If no other views are visible,
314   // returns null.
315   View* NextVisibleView(View::Views::const_iterator pos) const;
316 
317   // Return the first visible view in the host or nullptr if none are visible.
318   View* FirstVisibleView() const;
319 
320   // Return the last visible view in the host or nullptr if none are visible.
321   View* LastVisibleView() const;
322 
323   Orientation orientation_;
324 
325   // Spacing between child views and host view border.
326   gfx::Insets inside_border_insets_;
327 
328   // Spacing to put in between child views.
329   int between_child_spacing_;
330 
331   // The alignment of children in the main axis. This is
332   // MainAxisAlignment::kStart by default.
333   MainAxisAlignment main_axis_alignment_ = MainAxisAlignment::kStart;
334 
335   // The alignment of children in the cross axis. This is
336   // kStretch by default.
337   CrossAxisAlignment cross_axis_alignment_ = CrossAxisAlignment::kStretch;
338 
339   // A map of views to their flex weights.
340   FlexMap flex_map_;
341 
342   // The flex weight for views if none is set. Defaults to 0.
343   int default_flex_ = 0;
344 
345   // The minimum cross axis size for the layout.
346   int minimum_cross_axis_size_ = 0;
347 
348   // Adjacent view margins and spacing should be collapsed.
349   bool collapse_margins_spacing_;
350 
351   // The view that this BoxLayout is managing the layout for.
352   views::View* host_ = nullptr;
353 };
354 
355 }  // namespace views
356 
357 #endif  // UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
358