flex.dart 44.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// 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 'layer.dart';
12
import 'layout_helper.dart';
13
import 'object.dart';
14

Adam Barth's avatar
Adam Barth committed
15
/// How the child is inscribed into the available space.
16 17 18 19 20 21 22
///
/// 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
23 24
enum FlexFit {
  /// The child is forced to fill the available space.
25 26
  ///
  /// The [Expanded] widget assigns this kind of [FlexFit] to its child.
Adam Barth's avatar
Adam Barth committed
27 28 29 30
  tight,

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

Adam Barth's avatar
Adam Barth committed
36
/// Parent data for use with [RenderFlex].
37
class FlexParentData extends ContainerBoxParentData<RenderBox> {
38
  /// The flex factor to use for this child.
39
  ///
Adam Barth's avatar
Adam Barth committed
40 41 42 43
  /// 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.
44
  int? flex;
45

Adam Barth's avatar
Adam Barth committed
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).
53
  FlexFit? fit;
Adam Barth's avatar
Adam Barth committed
54

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

59
/// How much space should be occupied in the main axis.
60 61 62 63
///
/// 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
64
/// space, subject to the incoming layout constraints.
65
///
66 67 68 69 70 71 72
/// 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.
73
enum MainAxisSize {
74 75 76 77 78 79
  /// 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.
80 81 82 83 84
  ///
  /// 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.
85 86
  min,

87 88 89 90 91 92
  /// 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.
93 94 95 96
  ///
  /// 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.
97 98 99
  max,
}

100
/// How the children should be placed along the main axis in a flex layout.
101 102 103 104 105
///
/// See also:
///
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [RenderFlex], the flex render object.
106
enum MainAxisAlignment {
107
  /// Place the children as close to the start of the main axis as possible.
108 109 110 111 112 113
  ///
  /// 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.
114
  start,
115 116

  /// Place the children as close to the end of the main axis as possible.
117 118 119 120 121 122
  ///
  /// 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.
123
  end,
124 125

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

  /// Place the free space evenly between the children.
129
  spaceBetween,
130

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

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

140
/// How the children should be placed along the cross axis in a flex layout.
141 142 143 144 145
///
/// See also:
///
///  * [Column], [Row], and [Flex], the flex widgets.
///  * [RenderFlex], the flex render object.
146
enum CrossAxisAlignment {
147 148 149
  /// Place the children with their start edge aligned with the start side of
  /// the cross axis.
  ///
150 151 152 153 154 155 156 157 158
  /// 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.
159
  start,
160 161

  /// Place the children as close to the end of the cross axis as possible.
162
  ///
163 164 165 166 167 168 169 170 171
  /// 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.
172
  end,
173

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

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

  /// Place the children along the cross axis such that their baselines match.
187
  ///
188 189 190 191 192 193 194 195 196 197
  /// Because baselines are always horizontal, this alignment is intended for
  /// horizontal main axes. If the main axis is vertical, then this value is
  /// treated like [start].
  ///
  /// For horizontal main axes, if the minimum height constraint passed to the
  /// flex layout exceeds the intrinsic height of the cross axis, children will
  /// be aligned as close to the top as they can be while honoring the baseline
  /// alignment. In other words, the extra space will be below all the children.
  ///
  /// Children who report no baseline will be top-aligned.
198
  baseline,
199 200
}

201
bool? _startIsTopLeft(Axis direction, TextDirection? textDirection, VerticalDirection? verticalDirection) {
202 203 204 205 206 207 208 209
  // 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;
210 211
        case null:
          return null;
212 213 214 215 216 217 218
      }
    case Axis.vertical:
      switch (verticalDirection) {
        case VerticalDirection.down:
          return true;
        case VerticalDirection.up:
          return false;
219 220
        case null:
          return null;
221 222 223 224
      }
  }
}

225
typedef _ChildSizingFunction = double Function(RenderBox child, double extent);
226

227
/// Displays its children in a one-dimensional array.
228
///
229 230 231 232 233 234
/// ## Layout algorithm
///
/// _This section describes how the framework causes [RenderFlex] to position
/// its children._
/// _See [BoxConstraints] for an introduction to box layout models._
///
235
/// Layout for a [RenderFlex] proceeds in six steps:
236
///
237 238
/// 1. Layout each child with a null or zero flex factor with unbounded main
///    axis constraints and the incoming cross axis constraints. If the
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
///    [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.
269 270 271 272 273
///
/// See also:
///
///  * [Flex], the widget equivalent.
///  * [Row] and [Column], direction-specific variants of [Flex].
274
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
275 276
                                        RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
                                        DebugOverflowIndicatorMixin {
277 278 279 280
  /// 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.
281
  RenderFlex({
282
    List<RenderBox>? children,
283 284 285 286
    Axis direction = Axis.horizontal,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
287
    TextDirection? textDirection,
288
    VerticalDirection verticalDirection = VerticalDirection.down,
289
    TextBaseline? textBaseline,
290
    Clip clipBehavior = Clip.none,
291
  }) : _direction = direction,
292
       _mainAxisAlignment = mainAxisAlignment,
293
       _mainAxisSize = mainAxisSize,
294
       _crossAxisAlignment = crossAxisAlignment,
295 296
       _textDirection = textDirection,
       _verticalDirection = verticalDirection,
297 298
       _textBaseline = textBaseline,
       _clipBehavior = clipBehavior {
299 300 301
    addAll(children);
  }

302
  /// The direction to use as the main axis.
303 304
  Axis get direction => _direction;
  Axis _direction;
305
  set direction(Axis value) {
306 307 308 309 310 311
    if (_direction != value) {
      _direction = value;
      markNeedsLayout();
    }
  }

312
  /// How the children should be placed along the main axis.
313 314 315 316 317 318 319 320
  ///
  /// 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.
321 322
  MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
  MainAxisAlignment _mainAxisAlignment;
323
  set mainAxisAlignment(MainAxisAlignment value) {
324 325
    if (_mainAxisAlignment != value) {
      _mainAxisAlignment = value;
326 327 328 329
      markNeedsLayout();
    }
  }

330
  /// How much space should be occupied in the main axis.
331 332 333
  ///
  /// After allocating space to children, there might be some remaining free
  /// space. This value controls whether to maximize or minimize the amount of
334
  /// free space, subject to the incoming layout constraints.
335 336 337 338 339
  ///
  /// 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.
340 341
  MainAxisSize get mainAxisSize => _mainAxisSize;
  MainAxisSize _mainAxisSize;
342
  set mainAxisSize(MainAxisSize value) {
343 344 345 346 347 348
    if (_mainAxisSize != value) {
      _mainAxisSize = value;
      markNeedsLayout();
    }
  }

349
  /// How the children should be placed along the cross axis.
350 351 352 353 354 355 356 357
  ///
  /// 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.
358 359
  CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
  CrossAxisAlignment _crossAxisAlignment;
360
  set crossAxisAlignment(CrossAxisAlignment value) {
361 362
    if (_crossAxisAlignment != value) {
      _crossAxisAlignment = value;
363 364 365 366
      markNeedsLayout();
    }
  }

367 368 369
  /// Determines the order to lay children out horizontally and how to interpret
  /// `start` and `end` in the horizontal direction.
  ///
370 371
  /// 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
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
  /// 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.
387 388 389
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
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 419 420 421 422
    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();
    }
  }

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

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
  bool get _debugHasNecessaryDirections {
    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.');
        case Axis.vertical:
          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.');
        case Axis.vertical:
          break;
      }
454 455
    }
    if (crossAxisAlignment == CrossAxisAlignment.start ||
456 457 458 459 460 461 462 463 464 465 466
        crossAxisAlignment == CrossAxisAlignment.end) {
      switch (direction) {
        case Axis.horizontal:
          break;
        case Axis.vertical:
          assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
      }
    }
    return true;
  }

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

473
  /// {@macro flutter.material.Material.clipBehavior}
474 475 476 477 478 479 480 481 482 483 484 485
  ///
  /// Defaults to [Clip.none], and must not be null.
  Clip get clipBehavior => _clipBehavior;
  Clip _clipBehavior = Clip.none;
  set clipBehavior(Clip value) {
    if (value != _clipBehavior) {
      _clipBehavior = value;
      markNeedsPaint();
      markNeedsSemanticsUpdate();
    }
  }

486
  @override
487
  void setupParentData(RenderBox child) {
488
    if (child.parentData is! FlexParentData) {
489
      child.parentData = FlexParentData();
490
    }
491 492
  }

493 494
  bool get _canComputeIntrinsics => crossAxisAlignment != CrossAxisAlignment.baseline;

495
  double _getIntrinsicSize({
496 497 498
    required Axis sizingDirection,
    required double extent, // the extent in the direction that isn't the sizing direction
    required _ChildSizingFunction childSize, // a method to find the size in the sizing direction
499
  }) {
500
    if (!_canComputeIntrinsics) {
501 502 503 504 505
      // Intrinsics cannot be calculated without a full layout for
      // baseline alignment. Throw an assertion and return 0.0 as documented
      // on [RenderBox.computeMinIntrinsicWidth].
      assert(
        RenderObject.debugCheckingIntrinsics,
506
        'Intrinsics are not available for CrossAxisAlignment.baseline.',
507 508 509
      );
      return 0.0;
    }
510 511 512 513 514 515 516
    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;
517
      RenderBox? child = firstChild;
518
      while (child != null) {
519
        final int flex = _getFlex(child);
520 521
        totalFlex += flex;
        if (flex > 0) {
522
          final double flexFraction = childSize(child, extent) / _getFlex(child);
523 524
          maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
        } else {
525
          inflexibleSpace += childSize(child, extent);
526
        }
527
        final FlexParentData childParentData = child.parentData! as FlexParentData;
Hixie's avatar
Hixie committed
528
        child = childParentData.nextSibling;
529
      }
530
      return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
531 532
    } else {
      // INTRINSIC CROSS SIZE
533 534 535
      // 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.
536

537
      // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction.
538
      final double availableMainSpace = extent;
539 540 541
      int totalFlex = 0;
      double inflexibleSpace = 0.0;
      double maxCrossSize = 0.0;
542
      RenderBox? child = firstChild;
543
      while (child != null) {
544
        final int flex = _getFlex(child);
545
        totalFlex += flex;
546 547
        late final double mainSize;
        late final double crossSize;
548 549
        if (flex == 0) {
          switch (_direction) {
550 551 552 553 554 555
            case Axis.horizontal:
              mainSize = child.getMaxIntrinsicWidth(double.infinity);
              crossSize = childSize(child, mainSize);
            case Axis.vertical:
              mainSize = child.getMaxIntrinsicHeight(double.infinity);
              crossSize = childSize(child, mainSize);
556 557 558 559
          }
          inflexibleSpace += mainSize;
          maxCrossSize = math.max(maxCrossSize, crossSize);
        }
560
        final FlexParentData childParentData = child.parentData! as FlexParentData;
Hixie's avatar
Hixie committed
561
        child = childParentData.nextSibling;
562 563
      }

564
      // Determine the spacePerFlex by allocating the remaining available space.
565
      // When you're overconstrained spacePerFlex can be negative.
566
      final double spacePerFlex = math.max(0.0, (availableMainSpace - inflexibleSpace) / totalFlex);
567

568
      // Size remaining (flexible) items, find the maximum cross size.
569 570
      child = firstChild;
      while (child != null) {
571
        final int flex = _getFlex(child);
572
        if (flex > 0) {
573
          maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex));
574
        }
575
        final FlexParentData childParentData = child.parentData! as FlexParentData;
Hixie's avatar
Hixie committed
576
        child = childParentData.nextSibling;
577 578
      }

579
      return maxCrossSize;
580 581 582
    }
  }

583
  @override
584
  double computeMinIntrinsicWidth(double height) {
585
    return _getIntrinsicSize(
586
      sizingDirection: Axis.horizontal,
587
      extent: height,
588
      childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent),
589 590 591
    );
  }

592
  @override
593
  double computeMaxIntrinsicWidth(double height) {
594
    return _getIntrinsicSize(
595
      sizingDirection: Axis.horizontal,
596
      extent: height,
597
      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent),
598 599 600
    );
  }

601
  @override
602
  double computeMinIntrinsicHeight(double width) {
603
    return _getIntrinsicSize(
604
      sizingDirection: Axis.vertical,
605
      extent: width,
606
      childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent),
607 608 609
    );
  }

610
  @override
611
  double computeMaxIntrinsicHeight(double width) {
612
    return _getIntrinsicSize(
613
      sizingDirection: Axis.vertical,
614
      extent: width,
615
      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent),
616
    );
617 618
  }

619
  @override
620
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
621
    if (_direction == Axis.horizontal) {
622
      return defaultComputeDistanceToHighestActualBaseline(baseline);
623
    }
624 625 626 627
    return defaultComputeDistanceToFirstActualBaseline(baseline);
  }

  int _getFlex(RenderBox child) {
628
    final FlexParentData childParentData = child.parentData! as FlexParentData;
Adam Barth's avatar
Adam Barth committed
629 630 631 632
    return childParentData.flex ?? 0;
  }

  FlexFit _getFit(RenderBox child) {
633
    final FlexParentData childParentData = child.parentData! as FlexParentData;
Adam Barth's avatar
Adam Barth committed
634
    return childParentData.fit ?? FlexFit.tight;
635 636
  }

637
  double _getCrossSize(Size size) {
638 639
    switch (_direction) {
      case Axis.horizontal:
640
        return size.height;
641
      case Axis.vertical:
642
        return size.width;
643
    }
644 645
  }

646
  double _getMainSize(Size size) {
647 648
    switch (_direction) {
      case Axis.horizontal:
649
        return size.width;
650
      case Axis.vertical:
651
        return size.height;
652
    }
653 654
  }

655
  @override
656 657 658
  Size computeDryLayout(BoxConstraints constraints) {
    if (!_canComputeIntrinsics) {
      assert(debugCannotComputeDryLayout(
659
        reason: 'Dry layout cannot be computed for CrossAxisAlignment.baseline, which requires a full layout.',
660
      ));
661
      return Size.zero;
662 663 664 665 666 667 668 669 670 671 672
    }
    FlutterError? constraintsError;
    assert(() {
      constraintsError = _debugCheckConstraints(
        constraints: constraints,
        reportParentConstraints: false,
      );
      return true;
    }());
    if (constraintsError != null) {
      assert(debugCannotComputeDryLayout(error: constraintsError));
673
      return Size.zero;
674
    }
675

676 677 678 679
    final _LayoutSizes sizes = _computeSizes(
      layoutChild: ChildLayoutHelper.dryLayoutChild,
      constraints: constraints,
    );
680

681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
    switch (_direction) {
      case Axis.horizontal:
        return constraints.constrain(Size(sizes.mainSize, sizes.crossSize));
      case Axis.vertical:
        return constraints.constrain(Size(sizes.crossSize, sizes.mainSize));
    }
  }

  FlutterError? _debugCheckConstraints({required BoxConstraints constraints, required bool reportParentConstraints}) {
    FlutterError? result;
    assert(() {
      final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
      final bool canFlex = maxMainSize < double.infinity;
      RenderBox? child = firstChild;
      while (child != null) {
        final int flex = _getFlex(child);
        if (flex > 0) {
698 699 700
          final String identity = _direction == Axis.horizontal ? 'row' : 'column';
          final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
          final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
701 702
          DiagnosticsNode error, message;
          final List<DiagnosticsNode> addendum = <DiagnosticsNode>[];
703
          if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
704 705 706 707 708
            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 '
709
              'expand to fill the remaining space in the $axis direction.',
710
            );
711 712 713 714
            if (reportParentConstraints) { // Constraints of parents are unavailable in dry layout.
              RenderBox? node = this;
              switch (_direction) {
                case Axis.horizontal:
715
                  while (!node!.constraints.hasBoundedWidth && node.parent is RenderBox) {
716
                    node = node.parent! as RenderBox;
717 718
                  }
                  if (!node.constraints.hasBoundedWidth) {
719
                    node = null;
720
                  }
721
                case Axis.vertical:
722
                  while (!node!.constraints.hasBoundedHeight && node.parent is RenderBox) {
723
                    node = node.parent! as RenderBox;
724 725
                  }
                  if (!node.constraints.hasBoundedHeight) {
726
                    node = null;
727
                  }
728 729 730 731
              }
              if (node != null) {
                addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
              }
732
            }
733
            addendum.add(ErrorHint('See also: https://flutter.dev/layout/'));
734 735 736
          } else {
            return true;
          }
737
          result = FlutterError.fromParts(<DiagnosticsNode>[
738 739 740 741
            error,
            message,
            ErrorDescription(
              'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
742
              'cannot simultaneously expand to fit its parent.',
743 744 745 746 747 748
            ),
            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 '
749
              'rather than expanding to fit the maximum constraints provided by the parent.',
750 751 752 753
            ),
            ErrorDescription(
              'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
              '  https://flutter.dev/debugging/#rendering-layer\n'
754
              '  http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html',
755 756
            ),
            describeForError('The affected RenderFlex is', style: DiagnosticsTreeStyle.errorProperty),
757 758 759
            DiagnosticsProperty<dynamic>('The creator information is set to', debugCreator, style: DiagnosticsTreeStyle.errorProperty),
            ...addendum,
            ErrorDescription(
760
              "If none of the above helps enough to fix this problem, please don't hesitate to file a bug:\n"
761
              '  https://github.com/flutter/flutter/issues/new?template=2_bug.md',
762
            ),
763
          ]);
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
          return true;
        }
        child = childAfter(child);
      }
      return true;
    }());
    return result;
  }

  _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
    assert(_debugHasNecessaryDirections);

    // Determine used flex factor, size inflexible items, calculate free space.
    int totalFlex = 0;
    final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
    final bool canFlex = maxMainSize < double.infinity;

    double crossSize = 0.0;
    double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
    RenderBox? child = firstChild;
    RenderBox? lastFlexChild;
    while (child != null) {
      final FlexParentData childParentData = child.parentData! as FlexParentData;
      final int flex = _getFlex(child);
      if (flex > 0) {
789
        totalFlex += flex;
790
        lastFlexChild = child;
791
      } else {
792
        final BoxConstraints innerConstraints;
793
        if (crossAxisAlignment == CrossAxisAlignment.stretch) {
794
          switch (_direction) {
795
            case Axis.horizontal:
796
              innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
797
            case Axis.vertical:
798
              innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
799 800
          }
        } else {
801
          switch (_direction) {
802
            case Axis.horizontal:
803
              innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
804
            case Axis.vertical:
805
              innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
806
          }
807
        }
808 809 810
        final Size childSize = layoutChild(child, innerConstraints);
        allocatedSize += _getMainSize(childSize);
        crossSize = math.max(crossSize, _getCrossSize(childSize));
811
      }
Hixie's avatar
Hixie committed
812 813
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
814 815
    }

816
    // Distribute free space to flexible children.
817
    final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
818
    double allocatedFlexSpace = 0.0;
819 820
    if (totalFlex > 0) {
      final double spacePerFlex = canFlex ? (freeSpace / totalFlex) : double.nan;
821 822
      child = firstChild;
      while (child != null) {
Adam Barth's avatar
Adam Barth committed
823
        final int flex = _getFlex(child);
824
        if (flex > 0) {
825
          final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
826
          late final double minChildExtent;
Adam Barth's avatar
Adam Barth committed
827 828
          switch (_getFit(child)) {
            case FlexFit.tight:
829
              assert(maxChildExtent < double.infinity);
Adam Barth's avatar
Adam Barth committed
830 831 832 833
              minChildExtent = maxChildExtent;
            case FlexFit.loose:
              minChildExtent = 0.0;
          }
834
          final BoxConstraints innerConstraints;
835
          if (crossAxisAlignment == CrossAxisAlignment.stretch) {
836
            switch (_direction) {
837
              case Axis.horizontal:
838 839 840 841 842 843
                innerConstraints = BoxConstraints(
                  minWidth: minChildExtent,
                  maxWidth: maxChildExtent,
                  minHeight: constraints.maxHeight,
                  maxHeight: constraints.maxHeight,
                );
844
              case Axis.vertical:
845 846 847 848 849 850
                innerConstraints = BoxConstraints(
                  minWidth: constraints.maxWidth,
                  maxWidth: constraints.maxWidth,
                  minHeight: minChildExtent,
                  maxHeight: maxChildExtent,
                );
851 852 853
            }
          } else {
            switch (_direction) {
854
              case Axis.horizontal:
855 856 857 858 859
                innerConstraints = BoxConstraints(
                  minWidth: minChildExtent,
                  maxWidth: maxChildExtent,
                  maxHeight: constraints.maxHeight,
                );
860
              case Axis.vertical:
861 862 863 864 865
                innerConstraints = BoxConstraints(
                  maxWidth: constraints.maxWidth,
                  minHeight: minChildExtent,
                  maxHeight: maxChildExtent,
                );
866
            }
867
          }
868 869 870 871
          final Size childSize = layoutChild(child, innerConstraints);
          final double childMainSize = _getMainSize(childSize);
          assert(childMainSize <= maxChildExtent);
          allocatedSize += childMainSize;
872
          allocatedFlexSpace += maxChildExtent;
873
          crossSize = math.max(crossSize, _getCrossSize(childSize));
874
        }
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
        final FlexParentData childParentData = child.parentData! as FlexParentData;
        child = childParentData.nextSibling;
      }
    }

    final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
    return _LayoutSizes(
      mainSize: idealSize,
      crossSize: crossSize,
      allocatedSize: allocatedSize,
    );
  }

  @override
  void performLayout() {
    assert(_debugHasNecessaryDirections);
    final BoxConstraints constraints = this.constraints;
    assert(() {
      final FlutterError? constraintsError = _debugCheckConstraints(
        constraints: constraints,
        reportParentConstraints: true,
      );
      if (constraintsError != null) {
        throw constraintsError;
      }
      return true;
    }());

    final _LayoutSizes sizes = _computeSizes(
      layoutChild: ChildLayoutHelper.layoutChild,
      constraints: constraints,
    );

    final double allocatedSize = sizes.allocatedSize;
    double actualSize = sizes.mainSize;
    double crossSize = sizes.crossSize;
    double maxBaselineDistance = 0.0;
    if (crossAxisAlignment == CrossAxisAlignment.baseline) {
      RenderBox? child = firstChild;
      double maxSizeAboveBaseline = 0;
      double maxSizeBelowBaseline = 0;
      while (child != null) {
        assert(() {
918
          if (textBaseline == null) {
919
            throw FlutterError('To use FlexAlignItems.baseline, you must also specify which baseline to use using the "baseline" argument.');
920
          }
921 922 923 924 925 926 927 928 929 930 931 932 933 934
          return true;
        }());
        final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
        if (distance != null) {
          maxBaselineDistance = math.max(maxBaselineDistance, distance);
          maxSizeAboveBaseline = math.max(
            distance,
            maxSizeAboveBaseline,
          );
          maxSizeBelowBaseline = math.max(
            child.size.height - distance,
            maxSizeBelowBaseline,
          );
          crossSize = math.max(maxSizeAboveBaseline + maxSizeBelowBaseline, crossSize);
935
        }
936
        final FlexParentData childParentData = child.parentData! as FlexParentData;
Hixie's avatar
Hixie committed
937
        child = childParentData.nextSibling;
938
      }
939 940
    }

941
    // Align items along the main axis.
942 943
    switch (_direction) {
      case Axis.horizontal:
944
        size = constraints.constrain(Size(actualSize, crossSize));
945 946 947
        actualSize = size.width;
        crossSize = size.height;
      case Axis.vertical:
948
        size = constraints.constrain(Size(crossSize, actualSize));
949 950
        actualSize = size.height;
        crossSize = size.width;
951
    }
952
    final double actualSizeDelta = actualSize - allocatedSize;
953 954
    _overflow = math.max(0.0, -actualSizeDelta);
    final double remainingSpace = math.max(0.0, actualSizeDelta);
955 956
    late final double leadingSpace;
    late final double betweenSpace;
957 958 959 960 961
    // 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
    // to flip, but that doesn't have any detectable effect.
962
    final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
963 964
    switch (_mainAxisAlignment) {
      case MainAxisAlignment.start:
965 966
        leadingSpace = 0.0;
        betweenSpace = 0.0;
967
      case MainAxisAlignment.end:
968 969
        leadingSpace = remainingSpace;
        betweenSpace = 0.0;
970
      case MainAxisAlignment.center:
971 972
        leadingSpace = remainingSpace / 2.0;
        betweenSpace = 0.0;
973
      case MainAxisAlignment.spaceBetween:
974
        leadingSpace = 0.0;
975
        betweenSpace = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0;
976
      case MainAxisAlignment.spaceAround:
977
        betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0;
978
        leadingSpace = betweenSpace / 2.0;
979
      case MainAxisAlignment.spaceEvenly:
980
        betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0;
981
        leadingSpace = betweenSpace;
982 983
    }

Collin Jackson's avatar
Collin Jackson committed
984
    // Position elements
985
    double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
986
    RenderBox? child = firstChild;
987
    while (child != null) {
988
      final FlexParentData childParentData = child.parentData! as FlexParentData;
989
      final double childCrossPosition;
990 991 992
      switch (_crossAxisAlignment) {
        case CrossAxisAlignment.start:
        case CrossAxisAlignment.end:
993 994 995
          childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
                               == (_crossAxisAlignment == CrossAxisAlignment.start)
                             ? 0.0
996
                             : crossSize - _getCrossSize(child.size);
997
        case CrossAxisAlignment.center:
998
          childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0;
999 1000
        case CrossAxisAlignment.stretch:
          childCrossPosition = 0.0;
1001
        case CrossAxisAlignment.baseline:
1002
          if (_direction == Axis.horizontal) {
1003
            assert(textBaseline != null);
1004
            final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
1005
            if (distance != null) {
1006
              childCrossPosition = maxBaselineDistance - distance;
1007
            } else {
1008
              childCrossPosition = 0.0;
1009
            }
1010 1011
          } else {
            childCrossPosition = 0.0;
1012
          }
1013
      }
1014
      if (flipMainAxis) {
1015
        childMainPosition -= _getMainSize(child.size);
1016
      }
1017
      switch (_direction) {
1018
        case Axis.horizontal:
1019
          childParentData.offset = Offset(childMainPosition, childCrossPosition);
1020
        case Axis.vertical:
1021
          childParentData.offset = Offset(childCrossPosition, childMainPosition);
1022
      }
1023 1024 1025
      if (flipMainAxis) {
        childMainPosition -= betweenSpace;
      } else {
1026
        childMainPosition += _getMainSize(child.size) + betweenSpace;
1027
      }
Hixie's avatar
Hixie committed
1028
      child = childParentData.nextSibling;
1029 1030 1031
    }
  }

1032
  @override
1033
  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
Adam Barth's avatar
Adam Barth committed
1034
    return defaultHitTestChildren(result, position: position);
1035 1036
  }

1037
  @override
1038
  void paint(PaintingContext context, Offset offset) {
1039
    if (!_hasOverflow) {
1040
      defaultPaint(context, offset);
1041
      return;
1042
    }
Collin Jackson's avatar
Collin Jackson committed
1043

1044
    // There's no point in drawing the children if we're empty.
1045
    if (size.isEmpty) {
1046
      return;
1047
    }
1048

1049 1050 1051 1052 1053 1054 1055 1056
    _clipRectLayer.layer = context.pushClipRect(
      needsCompositing,
      offset,
      Offset.zero & size,
      defaultPaint,
      clipBehavior: clipBehavior,
      oldLayer: _clipRectLayer.layer,
    );
1057

1058
    assert(() {
1059 1060
      final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
        ErrorDescription(
1061
          'The overflowing $runtimeType has an orientation of $_direction.',
1062 1063 1064 1065
        ),
        ErrorDescription(
          'The edge of the $runtimeType that is overflowing has been marked '
          'in the rendering with a yellow and black striped pattern. This is '
1066
          'usually caused by the contents being too big for the $runtimeType.',
1067 1068 1069 1070
        ),
        ErrorHint(
          'Consider applying a flex factor (e.g. using an Expanded widget) to '
          'force the children of the $runtimeType to fit within the available '
1071
          'space instead of being sized to their natural size.',
1072 1073 1074 1075 1076 1077
        ),
        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 '
1078
          'than a Flex, like a ListView.',
1079
        ),
1080
      ];
1081 1082 1083 1084

      // 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.
1085
      final Rect overflowChildRect;
1086
      switch (_direction) {
1087
        case Axis.horizontal:
1088
          overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
1089
        case Axis.vertical:
1090
          overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
1091
      }
1092
      paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
1093
      return true;
1094
    }());
1095
  }
Collin Jackson's avatar
Collin Jackson committed
1096

1097 1098 1099 1100 1101 1102 1103
  final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();

  @override
  void dispose() {
    _clipRectLayer.layer = null;
    super.dispose();
  }
1104

1105
  @override
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
  Rect? describeApproximatePaintClip(RenderObject child) {
    switch (clipBehavior) {
      case Clip.none:
        return null;
      case Clip.hardEdge:
      case Clip.antiAlias:
      case Clip.antiAliasWithSaveLayer:
        return _hasOverflow ? Offset.zero & size : null;
    }
  }

Hixie's avatar
Hixie committed
1117

1118
  @override
1119 1120
  String toStringShort() {
    String header = super.toStringShort();
Ian Hickson's avatar
Ian Hickson committed
1121
    if (!kReleaseMode) {
1122
      if (_hasOverflow) {
Ian Hickson's avatar
Ian Hickson committed
1123
        header += ' OVERFLOWING';
1124
      }
Ian Hickson's avatar
Ian Hickson committed
1125
    }
1126
    return header;
Collin Jackson's avatar
Collin Jackson committed
1127
  }
1128

1129
  @override
1130 1131
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1132 1133 1134 1135 1136 1137 1138
    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));
1139
  }
1140
}
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152

class _LayoutSizes {
  const _LayoutSizes({
    required this.mainSize,
    required this.crossSize,
    required this.allocatedSize,
  });

  final double mainSize;
  final double crossSize;
  final double allocatedSize;
}