flex.dart 41.2 KB
Newer Older
1 2 3 4 5 6
// Copyright 2015 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.

import 'dart:math' as math;

7 8
import 'package:flutter/foundation.dart';

9
import 'box.dart';
10
import 'debug_overflow_indicator.dart';
11
import 'object.dart';
12

Adam Barth's avatar
Adam Barth committed
13
/// How the child is inscribed into the available space.
14 15 16 17 18 19 20
///
/// See also:
///
///  * [RenderFlex], the flex render object.
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [Expanded], the widget equivalent of [tight].
///  * [Flexible], the widget equivalent of [loose].
Adam Barth's avatar
Adam Barth committed
21 22
enum FlexFit {
  /// The child is forced to fill the available space.
23 24
  ///
  /// The [Expanded] widget assigns this kind of [FlexFit] to its child.
Adam Barth's avatar
Adam Barth committed
25 26 27 28
  tight,

  /// The child can be at most as large as the available space (but is
  /// allowed to be smaller).
29 30
  ///
  /// The [Flexible] widget assigns this kind of [FlexFit] to its child.
Adam Barth's avatar
Adam Barth committed
31 32 33
  loose,
}

Adam Barth's avatar
Adam Barth committed
34
/// Parent data for use with [RenderFlex].
35
class FlexParentData extends ContainerBoxParentData<RenderBox> {
36 37
  /// The flex factor to use for this child
  ///
Adam Barth's avatar
Adam Barth committed
38 39 40 41
  /// If null or zero, the child is inflexible and determines its own size. If
  /// non-zero, the amount of space the child's can occupy in the main axis is
  /// determined by dividing the free space (after placing the inflexible
  /// children) according to the flex factors of the flexible children.
42 43
  int flex;

Adam Barth's avatar
Adam Barth committed
44 45 46 47 48 49 50 51 52
  /// How a flexible child is inscribed into the available space.
  ///
  /// If [flex] is non-zero, the [fit] determines whether the child fills the
  /// space the parent makes available during layout. If the fit is
  /// [FlexFit.tight], the child is required to fill the available space. If the
  /// fit is [FlexFit.loose], the child can be at most as large as the available
  /// space (but is allowed to be smaller).
  FlexFit fit;

53
  @override
54
  String toString() => '${super.toString()}; flex=$flex; fit=$fit';
55 56
}

57
/// How much space should be occupied in the main axis.
58 59 60 61
///
/// During a flex layout, available space along the main axis is allocated to
/// children. After allocating space, there might be some remaining free space.
/// This value controls whether to maximize or minimize the amount of free
62
/// space, subject to the incoming layout constraints.
63
///
64 65 66 67 68 69 70
/// See also:
///
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [Expanded] and [Flexible], the widgets that controls a flex widgets'
///    children's flex.
///  * [RenderFlex], the flex render object.
///  * [MainAxisAlignment], which controls how the free space is distributed.
71
enum MainAxisSize {
72 73 74 75 76 77
  /// Minimize the amount of free space along the main axis, subject to the
  /// incoming layout constraints.
  ///
  /// If the incoming layout constraints have a large enough
  /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still
  /// be a non-zero amount of free space.
78 79 80 81 82
  ///
  /// If the incoming layout constraints are unbounded, and any children have a
  /// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by
  /// [Expanded]), the [RenderFlex] will assert, because there would be infinite
  /// remaining free space and boxes cannot be given infinite size.
83 84
  min,

85 86 87 88 89 90
  /// Maximize the amount of free space along the main axis, subject to the
  /// incoming layout constraints.
  ///
  /// If the incoming layout constraints have a small enough
  /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still
  /// be no free space.
91 92 93 94
  ///
  /// If the incoming layout constraints are unbounded, the [RenderFlex] will
  /// assert, because there would be infinite remaining free space and boxes
  /// cannot be given infinite size.
95 96 97
  max,
}

98
/// How the children should be placed along the main axis in a flex layout.
99 100 101 102 103
///
/// See also:
///
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [RenderFlex], the flex render object.
104
enum MainAxisAlignment {
105
  /// Place the children as close to the start of the main axis as possible.
106 107 108 109 110 111
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the start is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the start is the top or the bottom.
112
  start,
113 114

  /// Place the children as close to the end of the main axis as possible.
115 116 117 118 119 120
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the end is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the end is the top or the bottom.
121
  end,
122 123

  /// Place the children as close to the middle of the main axis as possible.
124
  center,
125 126

  /// Place the free space evenly between the children.
127
  spaceBetween,
128

Adam Barth's avatar
Adam Barth committed
129 130
  /// Place the free space evenly between the children as well as half of that
  /// space before and after the first and last child.
131
  spaceAround,
132

Adam Barth's avatar
Adam Barth committed
133 134 135
  /// Place the free space evenly between the children as well as before and
  /// after the first and last child.
  spaceEvenly,
136 137
}

138
/// How the children should be placed along the cross axis in a flex layout.
139 140 141 142 143
///
/// See also:
///
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [RenderFlex], the flex render object.
144
enum CrossAxisAlignment {
145 146 147
  /// Place the children with their start edge aligned with the start side of
  /// the cross axis.
  ///
148 149 150 151 152 153 154 155 156
  /// For example, in a column (a flex with a vertical axis) whose
  /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
  /// children along the left edge of the column.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the start is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the start is the top or the bottom.
157
  start,
158 159

  /// Place the children as close to the end of the cross axis as possible.
160
  ///
161 162 163 164 165 166 167 168 169
  /// For example, in a column (a flex with a vertical axis) whose
  /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
  /// children along the right edge of the column.
  ///
  /// If this value is used in a horizontal direction, a [TextDirection] must be
  /// available to determine if the end is the left or the right.
  ///
  /// If this value is used in a vertical direction, a [VerticalDirection] must be
  /// available to determine if the end is the top or the bottom.
170
  end,
171

172 173 174 175
  /// Place the children so that their centers align with the middle of the
  /// cross axis.
  ///
  /// This is the default cross-axis alignment.
176
  center,
177 178

  /// Require the children to fill the cross axis.
179 180 181
  ///
  /// This causes the constraints passed to the children to be tight in the
  /// cross axis.
182
  stretch,
183 184

  /// Place the children along the cross axis such that their baselines match.
185 186 187
  ///
  /// If the main axis is vertical, then this value is treated like [start]
  /// (since baselines are always horizontal).
188
  baseline,
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
bool _startIsTopLeft(Axis direction, TextDirection textDirection, VerticalDirection verticalDirection) {
  assert(direction != null);
  // If the relevant value of textDirection or verticalDirection is null, this returns null too.
  switch (direction) {
    case Axis.horizontal:
      switch (textDirection) {
        case TextDirection.ltr:
          return true;
        case TextDirection.rtl:
          return false;
      }
      break;
    case Axis.vertical:
      switch (verticalDirection) {
        case VerticalDirection.down:
          return true;
        case VerticalDirection.up:
          return false;
      }
      break;
  }
  return null;
}

215
typedef _ChildSizingFunction = double Function(RenderBox child, double extent);
216

217
/// Displays its children in a one-dimensional array.
218
///
219 220 221 222 223 224
/// ## Layout algorithm
///
/// _This section describes how the framework causes [RenderFlex] to position
/// its children._
/// _See [BoxConstraints] for an introduction to box layout models._
///
225
/// Layout for a [RenderFlex] proceeds in six steps:
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
/// 1. Layout each child a null or zero flex factor with unbounded main axis
///    constraints and the incoming cross axis constraints. If the
///    [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight
///    cross axis constraints that match the incoming max extent in the cross
///    axis.
/// 2. Divide the remaining main axis space among the children with non-zero
///    flex factors according to their flex factor. For example, a child with a
///    flex factor of 2.0 will receive twice the amount of main axis space as a
///    child with a flex factor of 1.0.
/// 3. Layout each of the remaining children with the same cross axis
///    constraints as in step 1, but instead of using unbounded main axis
///    constraints, use max axis constraints based on the amount of space
///    allocated in step 2. Children with [Flexible.fit] properties that are
///    [FlexFit.tight] are given tight constraints (i.e., forced to fill the
///    allocated space), and children with [Flexible.fit] properties that are
///    [FlexFit.loose] are given loose constraints (i.e., not forced to fill the
///    allocated space).
/// 4. The cross axis extent of the [RenderFlex] is the maximum cross axis
///    extent of the children (which will always satisfy the incoming
///    constraints).
/// 5. The main axis extent of the [RenderFlex] is determined by the
///    [mainAxisSize] property. If the [mainAxisSize] property is
///    [MainAxisSize.max], then the main axis extent of the [RenderFlex] is the
///    max extent of the incoming main axis constraints. If the [mainAxisSize]
///    property is [MainAxisSize.min], then the main axis extent of the [Flex]
///    is the sum of the main axis extents of the children (subject to the
///    incoming constraints).
/// 6. Determine the position for each child according to the
///    [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
///    [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis
///    space that has not been allocated to children is divided evenly and
///    placed between the children.
259 260 261 262 263
///
/// See also:
///
///  * [Flex], the widget equivalent.
///  * [Row] and [Column], direction-specific variants of [Flex].
264
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
265 266
                                        RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
                                        DebugOverflowIndicatorMixin {
267 268 269 270
  /// Creates a flex render object.
  ///
  /// By default, the flex layout is horizontal and children are aligned to the
  /// start of the main axis and the center of the cross axis.
271
  RenderFlex({
272
    List<RenderBox> children,
273 274 275 276
    Axis direction = Axis.horizontal,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
277
    TextDirection textDirection,
278
    VerticalDirection verticalDirection = VerticalDirection.down,
279
    TextBaseline textBaseline,
280 281 282 283 284
  }) : assert(direction != null),
       assert(mainAxisAlignment != null),
       assert(mainAxisSize != null),
       assert(crossAxisAlignment != null),
       _direction = direction,
285
       _mainAxisAlignment = mainAxisAlignment,
286
       _mainAxisSize = mainAxisSize,
287
       _crossAxisAlignment = crossAxisAlignment,
288 289
       _textDirection = textDirection,
       _verticalDirection = verticalDirection,
290
       _textBaseline = textBaseline {
291 292 293
    addAll(children);
  }

294
  /// The direction to use as the main axis.
295 296
  Axis get direction => _direction;
  Axis _direction;
297
  set direction(Axis value) {
298
    assert(value != null);
299 300 301 302 303 304
    if (_direction != value) {
      _direction = value;
      markNeedsLayout();
    }
  }

305
  /// How the children should be placed along the main axis.
306 307 308 309 310 311 312 313
  ///
  /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is
  /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
  /// [textDirection] must not be null.
  ///
  /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is
  /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
  /// [verticalDirection] must not be null.
314 315
  MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
  MainAxisAlignment _mainAxisAlignment;
316
  set mainAxisAlignment(MainAxisAlignment value) {
317
    assert(value != null);
318 319
    if (_mainAxisAlignment != value) {
      _mainAxisAlignment = value;
320 321 322 323
      markNeedsLayout();
    }
  }

324
  /// How much space should be occupied in the main axis.
325 326 327
  ///
  /// After allocating space to children, there might be some remaining free
  /// space. This value controls whether to maximize or minimize the amount of
328
  /// free space, subject to the incoming layout constraints.
329 330 331 332 333
  ///
  /// If some children have a non-zero flex factors (and none have a fit of
  /// [FlexFit.loose]), they will expand to consume all the available space and
  /// there will be no remaining free space to maximize or minimize, making this
  /// value irrelevant to the final layout.
334 335
  MainAxisSize get mainAxisSize => _mainAxisSize;
  MainAxisSize _mainAxisSize;
336
  set mainAxisSize(MainAxisSize value) {
337
    assert(value != null);
338 339 340 341 342 343
    if (_mainAxisSize != value) {
      _mainAxisSize = value;
      markNeedsLayout();
    }
  }

344
  /// How the children should be placed along the cross axis.
345 346 347 348 349 350 351 352
  ///
  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [verticalDirection] must not be null.
  ///
  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [textDirection] must not be null.
353 354
  CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
  CrossAxisAlignment _crossAxisAlignment;
355
  set crossAxisAlignment(CrossAxisAlignment value) {
356
    assert(value != null);
357 358
    if (_crossAxisAlignment != value) {
      _crossAxisAlignment = value;
359 360 361 362
      markNeedsLayout();
    }
  }

363 364 365
  /// Determines the order to lay children out horizontally and how to interpret
  /// `start` and `end` in the horizontal direction.
  ///
366 367
  /// If the [direction] is [Axis.horizontal], this controls the order in which
  /// children are positioned (left-to-right or right-to-left), and the meaning
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 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
  /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
  /// [MainAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.horizontal], and either the
  /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
  /// [MainAxisAlignment.end], or there's more than one child, then the
  /// [textDirection] must not be null.
  ///
  /// If the [direction] is [Axis.vertical], this controls the meaning of the
  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
  /// [CrossAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [textDirection] must not be null.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection != value) {
      _textDirection = value;
      markNeedsLayout();
    }
  }

  /// Determines the order to lay children out vertically and how to interpret
  /// `start` and `end` in the vertical direction.
  ///
  /// If the [direction] is [Axis.vertical], this controls which order children
  /// are painted in (down or up), the meaning of the [mainAxisAlignment]
  /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
  /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
  /// more than one child, then the [verticalDirection] must not be null.
  ///
  /// If the [direction] is [Axis.horizontal], this controls the meaning of the
  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
  /// [CrossAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [verticalDirection] must not be null.
  VerticalDirection get verticalDirection => _verticalDirection;
  VerticalDirection _verticalDirection;
  set verticalDirection(VerticalDirection value) {
    if (_verticalDirection != value) {
      _verticalDirection = value;
      markNeedsLayout();
    }
  }

419
  /// If aligning items according to their baseline, which baseline to use.
420 421
  ///
  /// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline].
422
  TextBaseline get textBaseline => _textBaseline;
423
  TextBaseline _textBaseline;
424
  set textBaseline(TextBaseline value) {
425
    assert(_crossAxisAlignment != CrossAxisAlignment.baseline || value != null);
426 427 428 429 430 431
    if (_textBaseline != value) {
      _textBaseline = value;
      markNeedsLayout();
    }
  }

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
  bool get _debugHasNecessaryDirections {
    assert(direction != null);
    assert(crossAxisAlignment != null);
    if (firstChild != null && lastChild != firstChild) {
      // i.e. there's more than one child
      switch (direction) {
        case Axis.horizontal:
          assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
          break;
        case Axis.vertical:
          assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
          break;
      }
    }
    if (mainAxisAlignment == MainAxisAlignment.start ||
        mainAxisAlignment == MainAxisAlignment.end) {
      switch (direction) {
        case Axis.horizontal:
          assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
          break;
        case Axis.vertical:
          assert(verticalDirection != null, 'Vertical $runtimeType with $mainAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
          break;
      }
456 457
    }
    if (crossAxisAlignment == CrossAxisAlignment.start ||
458 459 460 461 462 463 464 465 466 467 468 469 470
        crossAxisAlignment == CrossAxisAlignment.end) {
      switch (direction) {
        case Axis.horizontal:
          assert(verticalDirection != null, 'Horizontal $runtimeType with $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
          break;
        case Axis.vertical:
          assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
          break;
      }
    }
    return true;
  }

471
  // Set during layout if overflow occurred on the main axis.
472
  double _overflow;
473 474 475
  // Check whether any meaningful overflow is present. Values below an epsilon
  // are treated as not overflowing.
  bool get _hasOverflow => _overflow > precisionErrorTolerance;
476

477
  @override
478
  void setupParentData(RenderBox child) {
479
    if (child.parentData is! FlexParentData)
480
      child.parentData = FlexParentData();
481 482
  }

483
  double _getIntrinsicSize({
484
    Axis sizingDirection,
485
    double extent, // the extent in the direction that isn't the sizing direction
486
    _ChildSizingFunction childSize, // a method to find the size in the sizing direction
487
  }) {
488 489 490 491 492 493 494 495 496
    if (_direction == sizingDirection) {
      // INTRINSIC MAIN SIZE
      // Intrinsic main size is the smallest size the flex container can take
      // while maintaining the min/max-content contributions of its flex items.
      double totalFlex = 0.0;
      double inflexibleSpace = 0.0;
      double maxFlexFractionSoFar = 0.0;
      RenderBox child = firstChild;
      while (child != null) {
497
        final int flex = _getFlex(child);
498 499
        totalFlex += flex;
        if (flex > 0) {
500
          final double flexFraction = childSize(child, extent) / _getFlex(child);
501 502
          maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
        } else {
503
          inflexibleSpace += childSize(child, extent);
504
        }
Hixie's avatar
Hixie committed
505 506
        final FlexParentData childParentData = child.parentData;
        child = childParentData.nextSibling;
507
      }
508
      return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
509 510
    } else {
      // INTRINSIC CROSS SIZE
511 512 513
      // Intrinsic cross size is the max of the intrinsic cross sizes of the
      // children, after the flexible children are fit into the available space,
      // with the children sized using their max intrinsic dimensions.
514

515
      // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction.
516
      final double availableMainSpace = extent;
517 518 519 520 521
      int totalFlex = 0;
      double inflexibleSpace = 0.0;
      double maxCrossSize = 0.0;
      RenderBox child = firstChild;
      while (child != null) {
522
        final int flex = _getFlex(child);
523 524 525 526 527
        totalFlex += flex;
        double mainSize;
        double crossSize;
        if (flex == 0) {
          switch (_direction) {
528 529 530 531 532 533 534 535
            case Axis.horizontal:
              mainSize = child.getMaxIntrinsicWidth(double.infinity);
              crossSize = childSize(child, mainSize);
              break;
            case Axis.vertical:
              mainSize = child.getMaxIntrinsicHeight(double.infinity);
              crossSize = childSize(child, mainSize);
              break;
536 537 538 539
          }
          inflexibleSpace += mainSize;
          maxCrossSize = math.max(maxCrossSize, crossSize);
        }
Hixie's avatar
Hixie committed
540 541
        final FlexParentData childParentData = child.parentData;
        child = childParentData.nextSibling;
542 543
      }

544
      // Determine the spacePerFlex by allocating the remaining available space.
545
      // When you're overconstrained spacePerFlex can be negative.
546
      final double spacePerFlex = math.max(0.0,
547
          (availableMainSpace - inflexibleSpace) / totalFlex);
548

549
      // Size remaining (flexible) items, find the maximum cross size.
550 551
      child = firstChild;
      while (child != null) {
552
        final int flex = _getFlex(child);
553 554
        if (flex > 0)
          maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex));
Hixie's avatar
Hixie committed
555 556
        final FlexParentData childParentData = child.parentData;
        child = childParentData.nextSibling;
557 558
      }

559
      return maxCrossSize;
560 561 562
    }
  }

563
  @override
564
  double computeMinIntrinsicWidth(double height) {
565
    return _getIntrinsicSize(
566
      sizingDirection: Axis.horizontal,
567
      extent: height,
568
      childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent),
569 570 571
    );
  }

572
  @override
573
  double computeMaxIntrinsicWidth(double height) {
574
    return _getIntrinsicSize(
575
      sizingDirection: Axis.horizontal,
576
      extent: height,
577
      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent),
578 579 580
    );
  }

581
  @override
582
  double computeMinIntrinsicHeight(double width) {
583
    return _getIntrinsicSize(
584
      sizingDirection: Axis.vertical,
585
      extent: width,
586
      childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent),
587 588 589
    );
  }

590
  @override
591
  double computeMaxIntrinsicHeight(double width) {
592
    return _getIntrinsicSize(
593
      sizingDirection: Axis.vertical,
594
      extent: width,
595
      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent),
596
    );
597 598
  }

599
  @override
600
  double computeDistanceToActualBaseline(TextBaseline baseline) {
601
    if (_direction == Axis.horizontal)
602 603 604 605 606
      return defaultComputeDistanceToHighestActualBaseline(baseline);
    return defaultComputeDistanceToFirstActualBaseline(baseline);
  }

  int _getFlex(RenderBox child) {
Hixie's avatar
Hixie committed
607
    final FlexParentData childParentData = child.parentData;
Adam Barth's avatar
Adam Barth committed
608 609 610 611 612 613
    return childParentData.flex ?? 0;
  }

  FlexFit _getFit(RenderBox child) {
    final FlexParentData childParentData = child.parentData;
    return childParentData.fit ?? FlexFit.tight;
614 615 616
  }

  double _getCrossSize(RenderBox child) {
617 618 619 620 621 622 623
    switch (_direction) {
      case Axis.horizontal:
        return child.size.height;
      case Axis.vertical:
        return child.size.width;
    }
    return null;
624 625 626
  }

  double _getMainSize(RenderBox child) {
627 628 629 630 631 632 633
    switch (_direction) {
      case Axis.horizontal:
        return child.size.width;
      case Axis.vertical:
        return child.size.height;
    }
    return null;
634 635
  }

636
  @override
637
  void performLayout() {
638
    assert(_debugHasNecessaryDirections);
639
    // Determine used flex factor, size inflexible items, calculate free space.
640 641 642
    int totalFlex = 0;
    int totalChildren = 0;
    assert(constraints != null);
643
    final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
644
    final bool canFlex = maxMainSize < double.infinity;
645 646

    double crossSize = 0.0;
647
    double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
648
    RenderBox child = firstChild;
649
    RenderBox lastFlexChild;
650
    while (child != null) {
Hixie's avatar
Hixie committed
651
      final FlexParentData childParentData = child.parentData;
652
      totalChildren++;
Adam Barth's avatar
Adam Barth committed
653
      final int flex = _getFlex(child);
654
      if (flex > 0) {
655
        assert(() {
656 657 658
          final String identity = _direction == Axis.horizontal ? 'row' : 'column';
          final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
          final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
659 660
          DiagnosticsNode error, message;
          final List<DiagnosticsNode> addendum = <DiagnosticsNode>[];
661
          if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
662 663 664 665 666 667 668
            error = ErrorSummary('RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.');
            message = ErrorDescription(
              'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
              'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
              'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
              'expand to fill the remaining space in the $axis direction.'
            );
669 670
            RenderBox node = this;
            switch (_direction) {
671
              case Axis.horizontal:
672 673 674 675 676
                while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
                  node = node.parent;
                if (!node.constraints.hasBoundedWidth)
                  node = null;
                break;
677
              case Axis.vertical:
678 679 680 681 682 683 684
                while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
                  node = node.parent;
                if (!node.constraints.hasBoundedHeight)
                  node = null;
                break;
            }
            if (node != null) {
685
              addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
686
            }
687
            addendum.add(ErrorHint('See also: https://flutter.dev/layout/'));
688 689 690
          } else {
            return true;
          }
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
          throw FlutterError.fromParts(<DiagnosticsNode>[
            error,
            message,
            ErrorDescription(
              'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
              'cannot simultaneously expand to fit its parent.'
            ),
            ErrorHint(
              'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible '
              'children (using Flexible rather than Expanded). This will allow the flexible children '
              'to size themselves to less than the infinite remaining space they would otherwise be '
              'forced to take, and then will cause the RenderFlex to shrink-wrap the children '
              'rather than expanding to fit the maximum constraints provided by the parent.'
            ),
            ErrorDescription(
              'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
              '  https://flutter.dev/debugging/#rendering-layer\n'
708
              '  http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html'
709 710
            ),
            describeForError('The affected RenderFlex is', style: DiagnosticsTreeStyle.errorProperty),
711 712 713 714 715
            DiagnosticsProperty<dynamic>('The creator information is set to', debugCreator, style: DiagnosticsTreeStyle.errorProperty),
            ...addendum,
            ErrorDescription(
              'If none of the above helps enough to fix this problem, please don\'t hesitate to file a bug:\n'
              '  https://github.com/flutter/flutter/issues/new?template=BUG.md'
716
            ),
717
          ]);
718
        }());
Hixie's avatar
Hixie committed
719
        totalFlex += childParentData.flex;
720
        lastFlexChild = child;
721 722
      } else {
        BoxConstraints innerConstraints;
723
        if (crossAxisAlignment == CrossAxisAlignment.stretch) {
724
          switch (_direction) {
725
            case Axis.horizontal:
726
              innerConstraints = BoxConstraints(minHeight: constraints.maxHeight,
727 728
                                                    maxHeight: constraints.maxHeight);
              break;
729
            case Axis.vertical:
730
              innerConstraints = BoxConstraints(minWidth: constraints.maxWidth,
731
                                                    maxWidth: constraints.maxWidth);
732 733 734
              break;
          }
        } else {
735
          switch (_direction) {
736
            case Axis.horizontal:
737
              innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
738
              break;
739
            case Axis.vertical:
740
              innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
741 742
              break;
          }
743 744
        }
        child.layout(innerConstraints, parentUsesSize: true);
745
        allocatedSize += _getMainSize(child);
746 747
        crossSize = math.max(crossSize, _getCrossSize(child));
      }
Hixie's avatar
Hixie committed
748 749
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
750 751
    }

752
    // Distribute free space to flexible children, and determine baseline.
753
    final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
754
    double allocatedFlexSpace = 0.0;
755
    double maxBaselineDistance = 0.0;
756
    if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
757
      final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
758
      child = firstChild;
759 760
      double maxSizeAboveBaseline = 0;
      double maxSizeBelowBaseline = 0;
761
      while (child != null) {
Adam Barth's avatar
Adam Barth committed
762
        final int flex = _getFlex(child);
763
        if (flex > 0) {
764
          final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
Adam Barth's avatar
Adam Barth committed
765 766 767
          double minChildExtent;
          switch (_getFit(child)) {
            case FlexFit.tight:
768
              assert(maxChildExtent < double.infinity);
Adam Barth's avatar
Adam Barth committed
769 770 771 772 773 774 775
              minChildExtent = maxChildExtent;
              break;
            case FlexFit.loose:
              minChildExtent = 0.0;
              break;
          }
          assert(minChildExtent != null);
776
          BoxConstraints innerConstraints;
777
          if (crossAxisAlignment == CrossAxisAlignment.stretch) {
778
            switch (_direction) {
779
              case Axis.horizontal:
780
                innerConstraints = BoxConstraints(minWidth: minChildExtent,
Adam Barth's avatar
Adam Barth committed
781
                                                      maxWidth: maxChildExtent,
782 783 784
                                                      minHeight: constraints.maxHeight,
                                                      maxHeight: constraints.maxHeight);
                break;
785
              case Axis.vertical:
786
                innerConstraints = BoxConstraints(minWidth: constraints.maxWidth,
787
                                                      maxWidth: constraints.maxWidth,
Adam Barth's avatar
Adam Barth committed
788 789
                                                      minHeight: minChildExtent,
                                                      maxHeight: maxChildExtent);
790 791 792 793
                break;
            }
          } else {
            switch (_direction) {
794
              case Axis.horizontal:
795
                innerConstraints = BoxConstraints(minWidth: minChildExtent,
Adam Barth's avatar
Adam Barth committed
796
                                                      maxWidth: maxChildExtent,
797 798
                                                      maxHeight: constraints.maxHeight);
                break;
799
              case Axis.vertical:
800
                innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth,
Adam Barth's avatar
Adam Barth committed
801 802
                                                      minHeight: minChildExtent,
                                                      maxHeight: maxChildExtent);
803 804
                break;
            }
805
          }
806
          child.layout(innerConstraints, parentUsesSize: true);
807 808 809 810
          final double childSize = _getMainSize(child);
          assert(childSize <= maxChildExtent);
          allocatedSize += childSize;
          allocatedFlexSpace += maxChildExtent;
811
          crossSize = math.max(crossSize, _getCrossSize(child));
812
        }
813
        if (crossAxisAlignment == CrossAxisAlignment.baseline) {
814 815
          assert(() {
            if (textBaseline == null)
816
              throw FlutterError('To use FlexAlignItems.baseline, you must also specify which baseline to use using the "baseline" argument.');
817
            return true;
818
          }());
819
          final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
820
          if (distance != null) {
821
            maxBaselineDistance = math.max(maxBaselineDistance, distance);
822 823 824 825 826 827 828 829 830 831
            maxSizeAboveBaseline = math.max(
              distance,
              maxSizeAboveBaseline,
            );
            maxSizeBelowBaseline = math.max(
              child.size.height - distance,
              maxSizeBelowBaseline,
            );
            crossSize = maxSizeAboveBaseline + maxSizeBelowBaseline;
          }
832
        }
Hixie's avatar
Hixie committed
833 834
        final FlexParentData childParentData = child.parentData;
        child = childParentData.nextSibling;
835
      }
836 837
    }

838
    // Align items along the main axis.
839 840
    final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
    double actualSize;
841
    double actualSizeDelta;
842 843
    switch (_direction) {
      case Axis.horizontal:
844
        size = constraints.constrain(Size(idealSize, crossSize));
845 846 847 848
        actualSize = size.width;
        crossSize = size.height;
        break;
      case Axis.vertical:
849
        size = constraints.constrain(Size(crossSize, idealSize));
850 851 852
        actualSize = size.height;
        crossSize = size.width;
        break;
853
    }
854
    actualSizeDelta = actualSize - allocatedSize;
855 856 857 858
    _overflow = math.max(0.0, -actualSizeDelta);
    final double remainingSpace = math.max(0.0, actualSizeDelta);
    double leadingSpace;
    double betweenSpace;
859 860 861 862 863
    // flipMainAxis is used to decide whether to lay out left-to-right/top-to-bottom (false), or
    // right-to-left/bottom-to-top (true). The _startIsTopLeft will return null if there's only
    // one child and the relevant direction is null, in which case we arbitrarily decide not to
    // flip, but that doesn't have any detectable effect.
    final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
864 865
    switch (_mainAxisAlignment) {
      case MainAxisAlignment.start:
866 867 868
        leadingSpace = 0.0;
        betweenSpace = 0.0;
        break;
869
      case MainAxisAlignment.end:
870 871 872
        leadingSpace = remainingSpace;
        betweenSpace = 0.0;
        break;
873
      case MainAxisAlignment.center:
874 875 876
        leadingSpace = remainingSpace / 2.0;
        betweenSpace = 0.0;
        break;
877
      case MainAxisAlignment.spaceBetween:
878 879 880
        leadingSpace = 0.0;
        betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
        break;
881
      case MainAxisAlignment.spaceAround:
882 883 884
        betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
        leadingSpace = betweenSpace / 2.0;
        break;
885 886 887 888
      case MainAxisAlignment.spaceEvenly:
        betweenSpace = totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
        leadingSpace = betweenSpace;
        break;
889 890
    }

Collin Jackson's avatar
Collin Jackson committed
891
    // Position elements
892
    double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
893 894
    child = firstChild;
    while (child != null) {
Hixie's avatar
Hixie committed
895
      final FlexParentData childParentData = child.parentData;
896
      double childCrossPosition;
897 898 899
      switch (_crossAxisAlignment) {
        case CrossAxisAlignment.start:
        case CrossAxisAlignment.end:
900 901 902 903
          childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
                               == (_crossAxisAlignment == CrossAxisAlignment.start)
                             ? 0.0
                             : crossSize - _getCrossSize(child);
904
          break;
905
        case CrossAxisAlignment.center:
906 907
          childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
          break;
908 909 910
        case CrossAxisAlignment.stretch:
          childCrossPosition = 0.0;
          break;
911
        case CrossAxisAlignment.baseline:
912
          childCrossPosition = 0.0;
913
          if (_direction == Axis.horizontal) {
914
            assert(textBaseline != null);
915
            final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
916 917 918 919
            if (distance != null)
              childCrossPosition = maxBaselineDistance - distance;
          }
          break;
920
      }
921 922
      if (flipMainAxis)
        childMainPosition -= _getMainSize(child);
923
      switch (_direction) {
924
        case Axis.horizontal:
925
          childParentData.offset = Offset(childMainPosition, childCrossPosition);
926
          break;
927
        case Axis.vertical:
928
          childParentData.offset = Offset(childCrossPosition, childMainPosition);
929 930
          break;
      }
931 932 933 934 935
      if (flipMainAxis) {
        childMainPosition -= betweenSpace;
      } else {
        childMainPosition += _getMainSize(child) + betweenSpace;
      }
Hixie's avatar
Hixie committed
936
      child = childParentData.nextSibling;
937 938 939
    }
  }

940
  @override
941
  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
942
    return defaultHitTestChildren(result, position: position);
943 944
  }

945
  @override
946
  void paint(PaintingContext context, Offset offset) {
947
    if (!_hasOverflow) {
948
      defaultPaint(context, offset);
949
      return;
950
    }
Collin Jackson's avatar
Collin Jackson committed
951

952 953 954 955
    // There's no point in drawing the children if we're empty.
    if (size.isEmpty)
      return;

956
    // We have overflow. Clip it.
957
    context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
958

959
    assert(() {
960
      // Only set this if it's null to save work. It gets reset to null if the
961
      // _direction changes.
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981
      final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
        ErrorDescription(
          'The overflowing $runtimeType has an orientation of $_direction.'
        ),
        ErrorDescription(
          'The edge of the $runtimeType that is overflowing has been marked '
          'in the rendering with a yellow and black striped pattern. This is '
          'usually caused by the contents being too big for the $runtimeType.'
        ),
        ErrorHint(
          'Consider applying a flex factor (e.g. using an Expanded widget) to '
          'force the children of the $runtimeType to fit within the available '
          'space instead of being sized to their natural size.'
        ),
        ErrorHint(
          'This is considered an error condition because it indicates that there '
          'is content that cannot be seen. If the content is legitimately bigger '
          'than the available space, consider clipping it with a ClipRect widget '
          'before putting it in the flex, or using a scrollable container rather '
          'than a Flex, like a ListView.'
982
        ),
983
      ];
984 985 986 987 988 989

      // Simulate a child rect that overflows by the right amount. This child
      // rect is never used for drawing, just for determining the overflow
      // location and amount.
      Rect overflowChildRect;
      switch (_direction) {
990
        case Axis.horizontal:
991
          overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
992
          break;
993
        case Axis.vertical:
994
          overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
995 996
          break;
      }
997
      paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
998
      return true;
999
    }());
1000
  }
Collin Jackson's avatar
Collin Jackson committed
1001

1002
  @override
1003
  Rect describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null;
Hixie's avatar
Hixie committed
1004

1005
  @override
1006 1007
  String toStringShort() {
    String header = super.toStringShort();
1008
    if (_overflow is double && _hasOverflow)
1009 1010
      header += ' OVERFLOWING';
    return header;
Collin Jackson's avatar
Collin Jackson committed
1011
  }
1012

1013
  @override
1014 1015
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1016 1017 1018 1019 1020 1021 1022
    properties.add(EnumProperty<Axis>('direction', direction));
    properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
    properties.add(EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize));
    properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
    properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
1023
  }
1024
}