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