1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
|
// 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_
|