summaryrefslogtreecommitdiff
path: root/chromium/ui/views/layout/flex_layout_types.cc
blob: a263d6f8598073993b97865ce3ae40bbeabb834c (plain)
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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/layout/flex_layout_types.h"

#include <algorithm>
#include <tuple>
#include <utility>

#include "base/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view.h"

namespace views {

namespace {

// Default Flex Rules ----------------------------------------------------------

constexpr MaximumFlexSizeRule kDefaultMaximumFlexSizeRule =
    MaximumFlexSizeRule::kPreferred;

class LazySize;

// Helper object that lazily returns either the width or height of a LazySize
// (see below).
class LazyDimension {
 public:
  LazyDimension(const LazySize* size, LayoutOrientation dimension)
      : size_(size), dimension_(dimension) {}

  int operator*() const { return get(); }
  int get() const;

 private:
  const raw_ptr<const LazySize> size_;
  LayoutOrientation dimension_;
};

// Some of a view's sizing methods can be expensive to compute. This provides
// a lazy-eval value that behaves like a smart pointer but is more lightweight
// than base::LazyInstance.
class LazySize {
 public:
  using SizeFunc = gfx::Size (View::*)() const;

  explicit LazySize(const View* view, SizeFunc size_func)
      : view_(view), size_func_(size_func) {}
  LazySize(const LazySize& other) = default;
  ~LazySize() = default;
  // Note: copy operator is implicitly deleted due to const data member.

  const gfx::Size* operator->() const { return get(); }
  const gfx::Size& operator*() const { return *get(); }
  const gfx::Size* get() const {
    if (!size_)
      size_ = (view_.get()->*size_func_)();
    return &size_.value();
  }
  LazyDimension width() const {
    return LazyDimension(this, LayoutOrientation::kHorizontal);
  }
  LazyDimension height() const {
    return LazyDimension(this, LayoutOrientation::kVertical);
  }

 private:
  const raw_ptr<const View> view_;
  SizeFunc size_func_;
  mutable absl::optional<gfx::Size> size_;
};

int LazyDimension::get() const {
  switch (dimension_) {
    case LayoutOrientation::kHorizontal:
      return (*size_)->width();
    case LayoutOrientation::kVertical:
      return (*size_)->height();
  }
}

// Interpolates a size between minimum, preferred size, and upper bound based on
// sizing rules, returning the resulting ideal size.
int InterpolateSize(MinimumFlexSizeRule minimum_size_rule,
                    MaximumFlexSizeRule maximum_size_rule,
                    int minimum_size,
                    int preferred_size,
                    LazyDimension maximum_size,
                    int available_size) {
  // A view may (mistakenly) report a minimum size larger than its preferred
  // size. While in principle this shouldn't happen, by the time we've gotten
  // here it's better to simply make sure the minimum and preferred don't
  // cross.
  minimum_size = std::min(minimum_size, preferred_size);

  // TODO(dfried): this could be rearranged to allow lazy evaluation of
  // minimum_size.
  if (available_size < minimum_size) {
    switch (minimum_size_rule) {
      case MinimumFlexSizeRule::kScaleToZero:
        return available_size;
      case MinimumFlexSizeRule::kPreferred:
        return preferred_size;
      case MinimumFlexSizeRule::kScaleToMinimum:
      case MinimumFlexSizeRule::kPreferredSnapToMinimum:
        return minimum_size;
      case MinimumFlexSizeRule::kScaleToMinimumSnapToZero:
      case MinimumFlexSizeRule::kPreferredSnapToZero:
        return 0;
    }
  }
  if (available_size < preferred_size) {
    switch (minimum_size_rule) {
      case MinimumFlexSizeRule::kPreferred:
        return preferred_size;
      case MinimumFlexSizeRule::kScaleToZero:
      case MinimumFlexSizeRule::kScaleToMinimum:
      case MinimumFlexSizeRule::kScaleToMinimumSnapToZero:
        return available_size;
      case MinimumFlexSizeRule::kPreferredSnapToMinimum:
        return minimum_size;
      case MinimumFlexSizeRule::kPreferredSnapToZero:
        return 0;
    }
  }
  switch (maximum_size_rule) {
    case MaximumFlexSizeRule::kPreferred:
      return preferred_size;
    case MaximumFlexSizeRule::kScaleToMaximum: {
      // A view may (mistakenly) report a maximum size smaller than its
      // preferred size. While in principle this shouldn't happen, by the
      // time we're here it's better to simply make sure the maximum and
      // preferred size don't cross.
      const int actual_maximum_size = std::max(preferred_size, *maximum_size);
      return std::min(available_size, actual_maximum_size);
    }
    case MaximumFlexSizeRule::kUnbounded:
      return available_size;
  }
}

gfx::Size GetPreferredSize(MinimumFlexSizeRule minimum_width_rule,
                           MaximumFlexSizeRule maximum_width_rule,
                           MinimumFlexSizeRule minimum_height_rule,
                           MaximumFlexSizeRule maximum_height_rule,
                           bool adjust_height_for_width,
                           const View* view,
                           const SizeBounds& size_bounds) {
  LazySize minimum_size(view, &View::GetMinimumSize);
  LazySize maximum_size(view, &View::GetMaximumSize);
  gfx::Size preferred = view->GetPreferredSize();

  int width;
  if (!size_bounds.width().is_bounded()) {
    // Not having a maximum size is different from having a large available
    // size; a view can't grow infinitely, so we go with its preferred size.
    width = preferred.width();
  } else {
    width = InterpolateSize(minimum_width_rule, maximum_width_rule,
                            minimum_size->width(), preferred.width(),
                            maximum_size.width(), size_bounds.width().value());
  }

  int preferred_height = preferred.height();
  if (adjust_height_for_width) {
    // The |adjust_height_for_width| flag is used in vertical layouts where we
    // want views to be able to adapt to the horizontal available space by
    // growing vertically. We therefore allow the horizontal size to shrink even
    // if there's otherwise no flex allowed.
    if (size_bounds.width() > 0)
      width = size_bounds.width().min_of(width);

    if (width < preferred.width()) {
      // Allow views that need to grow vertically when they're compressed
      // horizontally to do so.
      //
      // If we just went with GetHeightForWidth() we would have situations where
      // an empty text control wanted no (or very little) height which could
      // cause a layout to shrink vertically; we will always try to allocate at
      // least the view's reported preferred height.
      //
      // Note that this is an adjustment made for practical considerations, and
      // may not be "correct" in some absolute sense. Let's revisit at some
      // point.
      preferred_height =
          std::max(preferred_height, view->GetHeightForWidth(width));
    }
  }

  int height;
  if (!size_bounds.height().is_bounded()) {
    // Not having a maximum size is different from having a large available
    // size; a view can't grow infinitely, so we go with its preferred size.
    height = preferred_height;
  } else {
    height = InterpolateSize(
        minimum_height_rule, maximum_height_rule, minimum_size->height(),
        preferred_height, maximum_size.height(), size_bounds.height().value());
  }

  return gfx::Size(width, height);
}

}  // namespace

// FlexSpecification -----------------------------------------------------------

FlexSpecification::FlexSpecification()
    : rule_(base::BindRepeating(&GetPreferredSize,
                                MinimumFlexSizeRule::kPreferred,
                                MaximumFlexSizeRule::kPreferred,
                                MinimumFlexSizeRule::kPreferred,
                                MaximumFlexSizeRule::kPreferred,
                                false)) {}

FlexSpecification::FlexSpecification(FlexRule rule)
    : rule_(std::move(rule)), weight_(1) {}

FlexSpecification::FlexSpecification(MinimumFlexSizeRule minimum_size_rule,
                                     MaximumFlexSizeRule maximum_size_rule,
                                     bool adjust_height_for_width)
    : FlexSpecification(base::BindRepeating(&GetPreferredSize,
                                            minimum_size_rule,
                                            maximum_size_rule,
                                            minimum_size_rule,
                                            maximum_size_rule,
                                            adjust_height_for_width)) {}

FlexSpecification::FlexSpecification(
    LayoutOrientation orientation,
    MinimumFlexSizeRule minimum_main_axis_rule,
    MaximumFlexSizeRule maximum_main_axis_rule,
    bool adjust_height_for_width,
    MinimumFlexSizeRule minimum_cross_axis_rule)
    : FlexSpecification(base::BindRepeating(
          &GetPreferredSize,
          orientation == LayoutOrientation::kHorizontal
              ? minimum_main_axis_rule
              : minimum_cross_axis_rule,
          orientation == LayoutOrientation::kHorizontal
              ? maximum_main_axis_rule
              : kDefaultMaximumFlexSizeRule,
          orientation == LayoutOrientation::kVertical ? minimum_main_axis_rule
                                                      : minimum_cross_axis_rule,
          orientation == LayoutOrientation::kVertical
              ? maximum_main_axis_rule
              : kDefaultMaximumFlexSizeRule,
          adjust_height_for_width)) {}

FlexSpecification::FlexSpecification(const FlexSpecification& other) = default;

FlexSpecification& FlexSpecification::operator=(
    const FlexSpecification& other) = default;

FlexSpecification::~FlexSpecification() = default;

FlexSpecification FlexSpecification::WithWeight(int weight) const {
  DCHECK_GE(weight, 0);
  FlexSpecification spec = *this;
  spec.weight_ = weight;
  return spec;
}

FlexSpecification FlexSpecification::WithOrder(int order) const {
  DCHECK_GE(order, 1);
  FlexSpecification spec = *this;
  spec.order_ = order;
  return spec;
}

FlexSpecification FlexSpecification::WithAlignment(
    LayoutAlignment alignment) const {
  FlexSpecification spec = *this;
  spec.alignment_ = alignment;
  return spec;
}

// Inset1D ---------------------------------------------------------------------

void Inset1D::SetInsets(int leading, int trailing) {
  leading_ = leading;
  trailing_ = trailing;
}

void Inset1D::Expand(int leading, int trailing) {
  leading_ += leading;
  trailing_ += trailing;
}

bool Inset1D::operator==(const Inset1D& other) const {
  return std::tie(leading_, trailing_) ==
         std::tie(other.leading_, other.trailing_);
}

bool Inset1D::operator!=(const Inset1D& other) const {
  return !(*this == other);
}

bool Inset1D::operator<(const Inset1D& other) const {
  return std::tie(leading_, trailing_) <
         std::tie(other.leading_, other.trailing_);
}

std::string Inset1D::ToString() const {
  return base::StringPrintf("%d, %d", leading(), trailing());
}

// Span ------------------------------------------------------------------------

void Span::SetSpan(int start, int length) {
  start_ = start;
  length_ = std::max(0, length);
}

void Span::Expand(int leading, int trailing) {
  const int end = this->end();
  set_start(start_ - leading);
  set_end(end + trailing);
}

void Span::Inset(int leading, int trailing) {
  Expand(-leading, -trailing);
}

void Span::Inset(const Inset1D& insets) {
  Inset(insets.leading(), insets.trailing());
}

void Span::Center(const Span& container, const Inset1D& margins) {
  int remaining = container.length() - length();

  // Case 1: no room for any margins. Just center the span in the container,
  // with equal overflow on each side.
  if (remaining <= 0) {
    set_start(container.start() + base::ClampCeil(remaining * 0.5f));
    return;
  }

  // Case 2: room for only part of the margins.
  if (margins.size() > remaining) {
    float scale = static_cast<float>(remaining) / margins.size();
    set_start(container.start() + base::ClampRound(scale * margins.leading()));
    return;
  }

  // Case 3: room for both span and margins. Center the whole unit.
  remaining -= margins.size();
  set_start(container.start() + remaining / 2 + margins.leading());
}

void Span::Align(const Span& container,
                 LayoutAlignment alignment,
                 const Inset1D& margins) {
  switch (alignment) {
    case LayoutAlignment::kStart:
      set_start(container.start() + margins.leading());
      break;
    case LayoutAlignment::kEnd:
      set_start(container.end() - (margins.trailing() + length()));
      break;
    case LayoutAlignment::kCenter:
      Center(container, margins);
      break;
    case LayoutAlignment::kStretch:
      SetSpan(container.start() + margins.leading(),
              std::max(0, container.length() - margins.size()));
      break;
    case LayoutAlignment::kBaseline:
      NOTIMPLEMENTED();
      break;
  }
}

bool Span::operator==(const Span& other) const {
  return std::tie(start_, length_) == std::tie(other.start_, other.length_);
}

bool Span::operator!=(const Span& other) const {
  return !(*this == other);
}

bool Span::operator<(const Span& other) const {
  return std::tie(start_, length_) < std::tie(other.start_, other.length_);
}

std::string Span::ToString() const {
  return base::StringPrintf("%d [%d]", start(), length());
}

}  // namespace views