flex.dart 40.4 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 double _ChildSizingFunction(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
    Axis direction: Axis.horizontal,
274
    MainAxisSize mainAxisSize: MainAxisSize.max,
275 276
    MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
    CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
277 278 279
    TextDirection textDirection,
    VerticalDirection verticalDirection: VerticalDirection.down,
    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 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
  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;
      }
   }
   if (crossAxisAlignment == CrossAxisAlignment.start ||
        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
  @override
475
  void setupParentData(RenderBox child) {
476 477
    if (child.parentData is! FlexParentData)
      child.parentData = new FlexParentData();
478 479
  }

480
  double _getIntrinsicSize({
481
    Axis sizingDirection,
482 483 484
    double extent, // the extent in the direction that isn't the sizing direction
    _ChildSizingFunction childSize // a method to find the size in the sizing direction
  }) {
485 486 487 488 489 490 491 492 493
    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) {
494
        final int flex = _getFlex(child);
495 496
        totalFlex += flex;
        if (flex > 0) {
497
          final double flexFraction = childSize(child, extent) / _getFlex(child);
498 499
          maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
        } else {
500
          inflexibleSpace += childSize(child, extent);
501
        }
Hixie's avatar
Hixie committed
502 503
        final FlexParentData childParentData = child.parentData;
        child = childParentData.nextSibling;
504
      }
505
      return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
506 507
    } else {
      // INTRINSIC CROSS SIZE
508 509 510 511
      // 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.
      // TODO(ianh): Support baseline alignment.
512

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

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

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

557
      return maxCrossSize;
558 559 560
    }
  }

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

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

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

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

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

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

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

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

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

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

    double crossSize = 0.0;
    double allocatedSize = 0.0; // Sum of the sizes of the the non-flexible children.
646
    RenderBox child = firstChild;
647
    RenderBox lastFlexChild;
648
    while (child != null) {
Hixie's avatar
Hixie committed
649
      final FlexParentData childParentData = child.parentData;
650
      totalChildren++;
Adam Barth's avatar
Adam Barth committed
651
      final int flex = _getFlex(child);
652
      if (flex > 0) {
653
        assert(() {
654 655 656
          final String identity = _direction == Axis.horizontal ? 'row' : 'column';
          final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
          final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
657 658
          String error, message;
          String addendum = '';
659
          if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
660 661 662
            error = 'RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.';
            message = '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 '
663
                      'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
664
                      'expand to fill the remaining space in the $axis direction.';
665
            final StringBuffer information = new StringBuffer();
666 667
            RenderBox node = this;
            switch (_direction) {
668
              case Axis.horizontal:
669 670 671 672 673
                while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
                  node = node.parent;
                if (!node.constraints.hasBoundedWidth)
                  node = null;
                break;
674
              case Axis.vertical:
675 676 677 678 679 680 681 682
                while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
                  node = node.parent;
                if (!node.constraints.hasBoundedHeight)
                  node = null;
                break;
            }
            if (node != null) {
              information.writeln('The nearest ancestor providing an unbounded width constraint is:');
683
              information.write('  ');
684
              information.write(node.toStringShallow(joiner: '\n  '));
685 686 687 688 689 690 691 692 693 694 695
            }
            information.writeln('See also: https://flutter.io/layout/');
            addendum = information.toString();
          } else {
            return true;
          }
          throw new FlutterError(
            '$error\n'
            '$message\n'
            'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
            'cannot simultaneously expand to fit its parent.\n'
696 697 698 699 700
            '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.\n'
701 702 703 704 705 706 707 708 709 710 711
            'The affected RenderFlex is:\n'
            '  $this\n'
            'The creator information is set to:\n'
            '  $debugCreator\n'
            '$addendum'
            'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
            '  https://flutter.io/debugging/#rendering-layer\n'
            '  http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html\n'
            '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'
          );
712
        }());
Hixie's avatar
Hixie committed
713
        totalFlex += childParentData.flex;
714
        lastFlexChild = child;
715 716
      } else {
        BoxConstraints innerConstraints;
717
        if (crossAxisAlignment == CrossAxisAlignment.stretch) {
718
          switch (_direction) {
719
            case Axis.horizontal:
720
              innerConstraints = new BoxConstraints(minHeight: constraints.maxHeight,
721 722
                                                    maxHeight: constraints.maxHeight);
              break;
723
            case Axis.vertical:
724
              innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
725
                                                    maxWidth: constraints.maxWidth);
726 727 728
              break;
          }
        } else {
729
          switch (_direction) {
730
            case Axis.horizontal:
731 732
              innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
              break;
733
            case Axis.vertical:
734 735 736
              innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
              break;
          }
737 738
        }
        child.layout(innerConstraints, parentUsesSize: true);
739
        allocatedSize += _getMainSize(child);
740 741
        crossSize = math.max(crossSize, _getCrossSize(child));
      }
Hixie's avatar
Hixie committed
742 743
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
744 745
    }

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

820
    // Align items along the main axis.
821 822
    final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
    double actualSize;
823
    double actualSizeDelta;
824 825 826 827 828 829 830 831 832 833 834
    switch (_direction) {
      case Axis.horizontal:
        size = constraints.constrain(new Size(idealSize, crossSize));
        actualSize = size.width;
        crossSize = size.height;
        break;
      case Axis.vertical:
        size = constraints.constrain(new Size(crossSize, idealSize));
        actualSize = size.height;
        crossSize = size.width;
        break;
835
    }
836
    actualSizeDelta = actualSize - allocatedSize;
837 838 839 840 841
    _overflow = math.max(0.0, -actualSizeDelta);

    final double remainingSpace = math.max(0.0, actualSizeDelta);
    double leadingSpace;
    double betweenSpace;
842 843 844 845 846
    // 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);
847 848
    switch (_mainAxisAlignment) {
      case MainAxisAlignment.start:
849 850 851
        leadingSpace = 0.0;
        betweenSpace = 0.0;
        break;
852
      case MainAxisAlignment.end:
853 854 855
        leadingSpace = remainingSpace;
        betweenSpace = 0.0;
        break;
856
      case MainAxisAlignment.center:
857 858 859
        leadingSpace = remainingSpace / 2.0;
        betweenSpace = 0.0;
        break;
860
      case MainAxisAlignment.spaceBetween:
861 862 863
        leadingSpace = 0.0;
        betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
        break;
864
      case MainAxisAlignment.spaceAround:
865 866 867
        betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
        leadingSpace = betweenSpace / 2.0;
        break;
868 869 870 871
      case MainAxisAlignment.spaceEvenly:
        betweenSpace = totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
        leadingSpace = betweenSpace;
        break;
872 873
    }

Collin Jackson's avatar
Collin Jackson committed
874
    // Position elements
875
    double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
876 877
    child = firstChild;
    while (child != null) {
Hixie's avatar
Hixie committed
878
      final FlexParentData childParentData = child.parentData;
879
      double childCrossPosition;
880 881 882
      switch (_crossAxisAlignment) {
        case CrossAxisAlignment.start:
        case CrossAxisAlignment.end:
883 884 885 886
          childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
                               == (_crossAxisAlignment == CrossAxisAlignment.start)
                             ? 0.0
                             : crossSize - _getCrossSize(child);
887
          break;
888
        case CrossAxisAlignment.center:
889 890
          childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
          break;
891 892 893
        case CrossAxisAlignment.stretch:
          childCrossPosition = 0.0;
          break;
894
        case CrossAxisAlignment.baseline:
895
          childCrossPosition = 0.0;
896
          if (_direction == Axis.horizontal) {
897
            assert(textBaseline != null);
898
            final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
899 900 901 902
            if (distance != null)
              childCrossPosition = maxBaselineDistance - distance;
          }
          break;
903
      }
904 905
      if (flipMainAxis)
        childMainPosition -= _getMainSize(child);
906
      switch (_direction) {
907
        case Axis.horizontal:
908
          childParentData.offset = new Offset(childMainPosition, childCrossPosition);
909
          break;
910
        case Axis.vertical:
911
          childParentData.offset = new Offset(childCrossPosition, childMainPosition);
912 913
          break;
      }
914 915 916 917 918
      if (flipMainAxis) {
        childMainPosition -= betweenSpace;
      } else {
        childMainPosition += _getMainSize(child) + betweenSpace;
      }
Hixie's avatar
Hixie committed
919
      child = childParentData.nextSibling;
920 921 922
    }
  }

923
  @override
924
  bool hitTestChildren(HitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
925
    return defaultHitTestChildren(result, position: position);
926 927
  }

928
  @override
929
  void paint(PaintingContext context, Offset offset) {
930
    if (_overflow <= 0.0) {
931
      defaultPaint(context, offset);
932
      return;
933
    }
Collin Jackson's avatar
Collin Jackson committed
934

935 936 937 938
    // There's no point in drawing the children if we're empty.
    if (size.isEmpty)
      return;

939
    // We have overflow. Clip it.
940
    context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
941

942
    assert(() {
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
      // Only set this if it's null to save work.  It gets reset to null if the
      // _direction changes.
      final String debugOverflowHints =
        'The overflowing $runtimeType has an orientation of $_direction.\n'
        '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. '
        '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.\n'
        '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.';

      // 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) {
964
        case Axis.horizontal:
965
          overflowChildRect = new Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
966
          break;
967
        case Axis.vertical:
968
          overflowChildRect = new Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
969 970
          break;
      }
971
      paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
972
      return true;
973
    }());
974
  }
Collin Jackson's avatar
Collin Jackson committed
975

976
  @override
977
  Rect describeApproximatePaintClip(RenderObject child) => _overflow > 0.0 ? Offset.zero & size : null;
Hixie's avatar
Hixie committed
978

979
  @override
980 981
  String toStringShort() {
    String header = super.toStringShort();
982
    if (_overflow is double && _overflow > 0.0)
983 984
      header += ' OVERFLOWING';
    return header;
Collin Jackson's avatar
Collin Jackson committed
985
  }
986

987
  @override
988
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
989
    super.debugFillProperties(description);
990 991 992 993 994 995 996
    description.add(new EnumProperty<Axis>('direction', direction));
    description.add(new EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
    description.add(new EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize));
    description.add(new EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    description.add(new EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
    description.add(new EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
997
  }
998
}