box.dart 95 KB
Newer Older
1 2 3 4 5
// 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;
6
import 'dart:ui' as ui show lerpDouble;
7

8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/gestures.dart';
10

11
import 'package:vector_math/vector_math_64.dart';
12

13 14 15
import 'debug.dart';
import 'object.dart';

16
// This class should only be used in debug builds.
17
class _DebugSize extends Size {
18
  _DebugSize(Size source, this._owner, this._canBeUsedByParent) : super.copy(source);
19 20 21 22
  final RenderBox _owner;
  final bool _canBeUsedByParent;
}

23
/// Immutable layout constraints for [RenderBox] layout.
24
///
25
/// A [Size] respects a [BoxConstraints] if, and only if, all of the following
26 27
/// relations hold:
///
28 29
/// * [minWidth] <= [Size.width] <= [maxWidth]
/// * [minHeight] <= [Size.height] <= [maxHeight]
30 31 32
///
/// The constraints themselves must satisfy these relations:
///
33 34
/// * 0.0 <= [minWidth] <= [maxWidth] <= [double.infinity]
/// * 0.0 <= [minHeight] <= [maxHeight] <= [double.infinity]
35
///
36
/// [double.infinity] is a legal value for each constraint.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
///
/// ## The box layout model
///
/// Render objects in the Flutter framework are laid out by a one-pass layout
/// model which walks down the render tree passing constraints, then walks back
/// up the render tree passing concrete geometry.
///
/// For boxes, the constraints are [BoxConstraints], which, as described herein,
/// consist of four numbers: a minimum width [minWidth], a maximum width
/// [maxWidth], a minimum height [minHeight], and a maximum height [maxHeight].
///
/// The geometry for boxes consists of a [Size], which must satisfy the
/// constraints described above.
///
/// Each [RenderBox] (the objects that provide the layout models for box
/// widgets) receives [BoxConstraints] from its parent, then lays out each of
/// its children, then picks a [Size] that satisfies the [BoxConstraints].
///
/// Render objects position their children independently of laying them out.
/// Frequently, the parent will use the children's sizes to determine their
/// position. A child does not know its position and will not necessarily be
/// laid out again, or repainted, if its position changes.
///
/// ## Terminology
///
/// When the minimum constraints and the maximum constraint in an axis are the
/// same, that axis is _tightly_ constrained. See: [new
/// BoxConstraints.tightFor], [new BoxConstraints.tightForFinite], [tighten],
/// [hasTightWidth], [hasTightHeight], [isTight].
///
/// An axis with a minimum constraint of 0.0 is _loose_ (regardless of the
/// maximum constraint; if it is also 0.0, then the axis is simultaneously tight
/// and loose!). See: [new BoxConstraints.loose], [loosen].
///
/// An axis whose maximum constraint is not infinite is _bounded_. See:
/// [hasBoundedWidth], [hasBoundedHeight].
///
/// An axis whose maximum constraint is infinite is _unbounded_. An axis is
/// _expanding_ if it is tightly infinite (its minimum and maximum constraints
/// are both infinite). See: [new BoxConstraints.expand].
///
/// A size is _constrained_ when it satisfies a [BoxConstraints] description.
/// See: [constrain], [constrainWidth], [constrainHeight],
/// [constrainDimensions], [constrainSizeAndAttemptToPreserveAspectRatio],
/// [isSatisfiedBy].
82
class BoxConstraints extends Constraints {
83
  /// Creates box constraints with the given constraints.
84
  const BoxConstraints({
85 86 87 88
    this.minWidth = 0.0,
    this.maxWidth = double.infinity,
    this.minHeight = 0.0,
    this.maxHeight = double.infinity
89 90
  });

91
  /// The minimum width that satisfies the constraints.
92
  final double minWidth;
93 94 95

  /// The maximum width that satisfies the constraints.
  ///
96
  /// Might be [double.infinity].
97
  final double maxWidth;
98 99

  /// The minimum height that satisfies the constraints.
100
  final double minHeight;
101 102 103

  /// The maximum height that satisfies the constraints.
  ///
104
  /// Might be [double.infinity].
105 106
  final double maxHeight;

107
  /// Creates box constraints that is respected only by the given size.
108 109 110 111 112 113
  BoxConstraints.tight(Size size)
    : minWidth = size.width,
      maxWidth = size.width,
      minHeight = size.height,
      maxHeight = size.height;

114
  /// Creates box constraints that require the given width or height.
115 116 117 118 119 120
  ///
  /// See also:
  ///
  ///  * [new BoxConstraints.tightForFinite], which is similar but instead of
  ///    being tight if the value is non-null, is tight if the value is not
  ///    infinite.
121 122 123 124
  const BoxConstraints.tightFor({
    double width,
    double height
  }): minWidth = width != null ? width : 0.0,
125
      maxWidth = width != null ? width : double.infinity,
126
      minHeight = height != null ? height : 0.0,
127
      maxHeight = height != null ? height : double.infinity;
128

129 130 131 132 133 134 135
  /// Creates box constraints that require the given width or height, except if
  /// they are infinite.
  ///
  /// See also:
  ///
  ///  * [new BoxConstraints.tightFor], which is similar but instead of being
  ///    tight if the value is not infinite, is tight if the value is non-null.
136
  const BoxConstraints.tightForFinite({
137 138
    double width = double.infinity,
    double height = double.infinity
139 140 141 142
  }): minWidth = width != double.infinity ? width : 0.0,
      maxWidth = width != double.infinity ? width : double.infinity,
      minHeight = height != double.infinity ? height : 0.0,
      maxHeight = height != double.infinity ? height : double.infinity;
143

144
  /// Creates box constraints that forbid sizes larger than the given size.
145 146 147 148 149 150
  BoxConstraints.loose(Size size)
    : minWidth = 0.0,
      maxWidth = size.width,
      minHeight = 0.0,
      maxHeight = size.height;

151
  /// Creates box constraints that expand to fill another box constraints.
152 153 154 155 156 157
  ///
  /// If width or height is given, the constraints will require exactly the
  /// given value in the given dimension.
  const BoxConstraints.expand({
    double width,
    double height
158 159 160 161
  }): minWidth = width != null ? width : double.infinity,
      maxWidth = width != null ? width : double.infinity,
      minHeight = height != null ? height : double.infinity,
      maxHeight = height != null ? height : double.infinity;
162

163
  /// Creates a copy of this box constraints but with the given fields replaced with the new values.
Hixie's avatar
Hixie committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177
  BoxConstraints copyWith({
    double minWidth,
    double maxWidth,
    double minHeight,
    double maxHeight
  }) {
    return new BoxConstraints(
      minWidth: minWidth ?? this.minWidth,
      maxWidth: maxWidth ?? this.maxWidth,
      minHeight: minHeight ?? this.minHeight,
      maxHeight: maxHeight ?? this.maxHeight
    );
  }

178
  /// Returns new box constraints that are smaller by the given edge dimensions.
179
  BoxConstraints deflate(EdgeInsets edges) {
180
    assert(edges != null);
181
    assert(debugAssertIsValid());
182 183 184 185
    final double horizontal = edges.horizontal;
    final double vertical = edges.vertical;
    final double deflatedMinWidth = math.max(0.0, minWidth - horizontal);
    final double deflatedMinHeight = math.max(0.0, minHeight - vertical);
186
    return new BoxConstraints(
187 188 189 190
      minWidth: deflatedMinWidth,
      maxWidth: math.max(deflatedMinWidth, maxWidth - horizontal),
      minHeight: deflatedMinHeight,
      maxHeight: math.max(deflatedMinHeight, maxHeight - vertical)
191 192 193
    );
  }

194
  /// Returns new box constraints that remove the minimum width and height requirements.
195
  BoxConstraints loosen() {
196
    assert(debugAssertIsValid());
197 198 199 200 201 202 203 204
    return new BoxConstraints(
      minWidth: 0.0,
      maxWidth: maxWidth,
      minHeight: 0.0,
      maxHeight: maxHeight
    );
  }

205 206
  /// Returns new box constraints that respect the given constraints while being
  /// as close as possible to the original constraints.
207
  BoxConstraints enforce(BoxConstraints constraints) {
208
    return new BoxConstraints(
209 210 211 212
      minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
      maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
      minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),
      maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight)
213 214 215
    );
  }

216 217 218 219
  /// Returns new box constraints with a tight width and/or height as close to
  /// the given width and height as possible while still respecting the original
  /// box constraints.
  BoxConstraints tighten({ double width, double height }) {
220 221 222 223
    return new BoxConstraints(minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth),
                              maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth),
                              minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight),
                              maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight));
224 225
  }

226 227 228 229 230 231 232 233 234 235
  /// A box constraints with the width and height constraints flipped.
  BoxConstraints get flipped {
    return new BoxConstraints(
      minWidth: minHeight,
      maxWidth: maxHeight,
      minHeight: minWidth,
      maxHeight: maxWidth
    );
  }

236
  /// Returns box constraints with the same width constraints but with
237
  /// unconstrained height.
238 239
  BoxConstraints widthConstraints() => new BoxConstraints(minWidth: minWidth, maxWidth: maxWidth);

240
  /// Returns box constraints with the same height constraints but with
241
  /// unconstrained width
242 243
  BoxConstraints heightConstraints() => new BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);

244 245
  /// Returns the width that both satisfies the constraints and is as close as
  /// possible to the given width.
246
  double constrainWidth([double width = double.infinity]) {
247
    assert(debugAssertIsValid());
248
    return width.clamp(minWidth, maxWidth);
249 250
  }

251 252
  /// Returns the height that both satisfies the constraints and is as close as
  /// possible to the given height.
253
  double constrainHeight([double height = double.infinity]) {
254
    assert(debugAssertIsValid());
255
    return height.clamp(minHeight, maxHeight);
256 257
  }

258
  Size _debugPropagateDebugSize(Size size, Size result) {
259 260 261 262
    assert(() {
      if (size is _DebugSize)
        result = new _DebugSize(result, size._owner, size._canBeUsedByParent);
      return true;
263
    }());
264 265
    return result;
  }
266

267 268 269 270 271 272 273
  /// Returns the size that both satisfies the constraints and is as close as
  /// possible to the given size.
  ///
  /// See also [constrainDimensions], which applies the same algorithm to
  /// separately provided widths and heights.
  Size constrain(Size size) {
    Size result = new Size(constrainWidth(size.width), constrainHeight(size.height));
274
    assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
275 276 277 278 279 280 281 282 283 284 285 286
    return result;
  }

  /// Returns the size that both satisfies the constraints and is as close as
  /// possible to the given width and height.
  ///
  /// When you already have a [Size], prefer [constrain], which applies the same
  /// algorithm to a [Size] directly.
  Size constrainDimensions(double width, double height) {
    return new Size(constrainWidth(width), constrainHeight(height));
  }

Adam Barth's avatar
Adam Barth committed
287 288
  /// Returns a size that attempts to meet the following conditions, in order:
  ///
289 290
  ///  * The size must satisfy these constraints.
  ///  * The aspect ratio of the returned size matches the aspect ratio of the
Adam Barth's avatar
Adam Barth committed
291
  ///    given size.
292
  ///  * The returned size as big as possible while still being equal to or
Adam Barth's avatar
Adam Barth committed
293 294
  ///    smaller than the given size.
  Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) {
295 296
    if (isTight) {
      Size result = smallest;
297
      assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
298 299
      return result;
    }
Adam Barth's avatar
Adam Barth committed
300 301 302 303 304

    double width = size.width;
    double height = size.height;
    assert(width > 0.0);
    assert(height > 0.0);
305
    final double aspectRatio = width / height;
Adam Barth's avatar
Adam Barth committed
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

    if (width > maxWidth) {
      width = maxWidth;
      height = width / aspectRatio;
    }

    if (height > maxHeight) {
      height = maxHeight;
      width = height * aspectRatio;
    }

    if (width < minWidth) {
      width = minWidth;
      height = width / aspectRatio;
    }

    if (height < minHeight) {
      height = minHeight;
      width = height * aspectRatio;
    }

327
    Size result = new Size(constrainWidth(width), constrainHeight(height));
328
    assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
329
    return result;
Adam Barth's avatar
Adam Barth committed
330 331
  }

332
  /// The biggest size that satisfies the constraints.
333 334
  Size get biggest => new Size(constrainWidth(), constrainHeight());

335
  /// The smallest size that satisfies the constraints.
336
  Size get smallest => new Size(constrainWidth(0.0), constrainHeight(0.0));
337

338
  /// Whether there is exactly one width value that satisfies the constraints.
339
  bool get hasTightWidth => minWidth >= maxWidth;
340

341
  /// Whether there is exactly one height value that satisfies the constraints.
342
  bool get hasTightHeight => minHeight >= maxHeight;
343

344
  /// Whether there is exactly one size that satisfies the constraints.
345
  @override
346 347
  bool get isTight => hasTightWidth && hasTightHeight;

348
  /// Whether there is an upper bound on the maximum width.
349
  bool get hasBoundedWidth => maxWidth < double.infinity;
350 351

  /// Whether there is an upper bound on the maximum height.
352
  bool get hasBoundedHeight => maxHeight < double.infinity;
353

354
  /// Whether the given size satisfies the constraints.
355
  bool isSatisfiedBy(Size size) {
356
    assert(debugAssertIsValid());
357 358
    return (minWidth <= size.width) && (size.width <= maxWidth) &&
           (minHeight <= size.height) && (size.height <= maxHeight);
359 360
  }

361 362
  /// Scales each constraint parameter by the given factor.
  BoxConstraints operator*(double factor) {
Adam Barth's avatar
Adam Barth committed
363
    return new BoxConstraints(
364 365 366 367
      minWidth: minWidth * factor,
      maxWidth: maxWidth * factor,
      minHeight: minHeight * factor,
      maxHeight: maxHeight * factor
Adam Barth's avatar
Adam Barth committed
368 369 370
    );
  }

371 372
  /// Scales each constraint parameter by the inverse of the given factor.
  BoxConstraints operator/(double factor) {
Adam Barth's avatar
Adam Barth committed
373
    return new BoxConstraints(
374 375 376 377
      minWidth: minWidth / factor,
      maxWidth: maxWidth / factor,
      minHeight: minHeight / factor,
      maxHeight: maxHeight / factor
Adam Barth's avatar
Adam Barth committed
378 379 380
    );
  }

381 382
  /// Scales each constraint parameter by the inverse of the given factor, rounded to the nearest integer.
  BoxConstraints operator~/(double factor) {
Adam Barth's avatar
Adam Barth committed
383
    return new BoxConstraints(
384 385 386 387
      minWidth: (minWidth ~/ factor).toDouble(),
      maxWidth: (maxWidth ~/ factor).toDouble(),
      minHeight: (minHeight ~/ factor).toDouble(),
      maxHeight: (maxHeight ~/ factor).toDouble()
Adam Barth's avatar
Adam Barth committed
388 389 390
    );
  }

391 392
  /// Computes the remainder of each constraint parameter by the given value.
  BoxConstraints operator%(double value) {
Adam Barth's avatar
Adam Barth committed
393
    return new BoxConstraints(
394 395 396 397
      minWidth: minWidth % value,
      maxWidth: maxWidth % value,
      minHeight: minHeight % value,
      maxHeight: maxHeight % value
Adam Barth's avatar
Adam Barth committed
398 399 400
    );
  }

401
  /// Linearly interpolate between two BoxConstraints.
Adam Barth's avatar
Adam Barth committed
402
  ///
403 404
  /// If either is null, this function interpolates from a [BoxConstraints]
  /// object whose fields are all set to 0.0.
405 406 407 408 409 410 411 412 413 414 415 416
  ///
  /// The `t` argument represents position on the timeline, with 0.0 meaning
  /// that the interpolation has not started, returning `a` (or something
  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
  /// returning `b` (or something equivalent to `b`), and values in between
  /// meaning that the interpolation is at the relevant point on the timeline
  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
  /// 1.0, so negative values and values greater than 1.0 are valid (and can
  /// easily be generated by curves such as [Curves.elasticInOut]).
  ///
  /// Values for `t` are usually obtained from an [Animation<double>], such as
  /// an [AnimationController].
Adam Barth's avatar
Adam Barth committed
417
  static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, double t) {
418
    assert(t != null);
Adam Barth's avatar
Adam Barth committed
419 420 421 422 423 424
    if (a == null && b == null)
      return null;
    if (a == null)
      return b * t;
    if (b == null)
      return a * (1.0 - t);
425 426
    assert(a.debugAssertIsValid());
    assert(b.debugAssertIsValid());
427 428 429 430
    assert((a.minWidth.isFinite && b.minWidth.isFinite) || (a.minWidth == double.infinity && b.minWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
    assert((a.maxWidth.isFinite && b.maxWidth.isFinite) || (a.maxWidth == double.infinity && b.maxWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
    assert((a.minHeight.isFinite && b.minHeight.isFinite) || (a.minHeight == double.infinity && b.minHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
    assert((a.maxHeight.isFinite && b.maxHeight.isFinite) || (a.maxHeight == double.infinity && b.maxHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
Adam Barth's avatar
Adam Barth committed
431
    return new BoxConstraints(
432 433 434 435
      minWidth: a.minWidth.isFinite ? ui.lerpDouble(a.minWidth, b.minWidth, t) : double.infinity,
      maxWidth: a.maxWidth.isFinite ? ui.lerpDouble(a.maxWidth, b.maxWidth, t) : double.infinity,
      minHeight: a.minHeight.isFinite ? ui.lerpDouble(a.minHeight, b.minHeight, t) : double.infinity,
      maxHeight: a.maxHeight.isFinite ? ui.lerpDouble(a.maxHeight, b.maxHeight, t) : double.infinity,
Adam Barth's avatar
Adam Barth committed
436 437 438
    );
  }

439
  /// Returns whether the object's constraints are normalized.
440
  /// Constraints are normalized if the minimums are less than or
441 442 443 444 445 446 447 448 449
  /// equal to the corresponding maximums.
  ///
  /// For example, a BoxConstraints object with a minWidth of 100.0
  /// and a maxWidth of 90.0 is not normalized.
  ///
  /// Most of the APIs on BoxConstraints expect the constraints to be
  /// normalized and have undefined behavior when they are not. In
  /// checked mode, many of these APIs will assert if the constraints
  /// are not normalized.
450
  @override
451 452 453 454 455 456
  bool get isNormalized {
    return minWidth >= 0.0 &&
           minWidth <= maxWidth &&
           minHeight >= 0.0 &&
           minHeight <= maxHeight;
  }
457

458
  @override
459
  bool debugAssertIsValid({
460
    bool isAppliedConstraint = false,
461
    InformationCollector informationCollector,
462
  }) {
463
    assert(() {
464
      void throwError(String message) {
465
        final StringBuffer information = new StringBuffer();
466 467 468 469
        if (informationCollector != null)
          informationCollector(information);
        throw new FlutterError('$message\n${information}The offending constraints were:\n  $this');
      }
470
      if (minWidth.isNaN || maxWidth.isNaN || minHeight.isNaN || maxHeight.isNaN) {
471
        final List<String> affectedFieldsList = <String>[];
472 473 474 475 476 477 478 479
        if (minWidth.isNaN)
          affectedFieldsList.add('minWidth');
        if (maxWidth.isNaN)
          affectedFieldsList.add('maxWidth');
        if (minHeight.isNaN)
          affectedFieldsList.add('minHeight');
        if (maxHeight.isNaN)
          affectedFieldsList.add('maxHeight');
480
        assert(affectedFieldsList.isNotEmpty);
481 482 483 484 485 486 487 488 489 490
        if (affectedFieldsList.length > 1)
          affectedFieldsList.add('and ${affectedFieldsList.removeLast()}');
        String whichFields = '';
        if (affectedFieldsList.length > 2) {
          whichFields = affectedFieldsList.join(', ');
        } else if (affectedFieldsList.length == 2) {
          whichFields = affectedFieldsList.join(' ');
        } else {
          whichFields = affectedFieldsList.single;
        }
491
        throwError('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.');
492
      }
493
      if (minWidth < 0.0 && minHeight < 0.0)
494
        throwError('BoxConstraints has both a negative minimum width and a negative minimum height.');
495
      if (minWidth < 0.0)
496
        throwError('BoxConstraints has a negative minimum width.');
497
      if (minHeight < 0.0)
498
        throwError('BoxConstraints has a negative minimum height.');
499
      if (maxWidth < minWidth && maxHeight < minHeight)
500
        throwError('BoxConstraints has both width and height constraints non-normalized.');
501
      if (maxWidth < minWidth)
502
        throwError('BoxConstraints has non-normalized width constraints.');
503
      if (maxHeight < minHeight)
504
        throwError('BoxConstraints has non-normalized height constraints.');
505 506
      if (isAppliedConstraint) {
        if (minWidth.isInfinite && minHeight.isInfinite)
507
          throwError('BoxConstraints forces an infinite width and infinite height.');
508
        if (minWidth.isInfinite)
509
          throwError('BoxConstraints forces an infinite width.');
510
        if (minHeight.isInfinite)
511
          throwError('BoxConstraints forces an infinite height.');
512 513 514
      }
      assert(isNormalized);
      return true;
515
    }());
516 517 518
    return isNormalized;
  }

519 520 521 522
  /// Returns a box constraints that [isNormalized].
  ///
  /// The returned [maxWidth] is at least as large as the [minWidth]. Similarly,
  /// the returned [maxHeight] is at least as large as the [minHeight].
523
  BoxConstraints normalize() {
524 525 526 527
    if (isNormalized)
      return this;
    final double minWidth = this.minWidth >= 0.0 ? this.minWidth : 0.0;
    final double minHeight = this.minHeight >= 0.0 ? this.minHeight : 0.0;
528 529 530 531 532 533 534 535
    return new BoxConstraints(
      minWidth: minWidth,
      maxWidth: minWidth > maxWidth ? minWidth : maxWidth,
      minHeight: minHeight,
      maxHeight: minHeight > maxHeight ? minHeight : maxHeight
    );
  }

536
  @override
Hixie's avatar
Hixie committed
537
  bool operator ==(dynamic other) {
538
    assert(debugAssertIsValid());
539 540
    if (identical(this, other))
      return true;
Hixie's avatar
Hixie committed
541 542 543
    if (other is! BoxConstraints)
      return false;
    final BoxConstraints typedOther = other;
544
    assert(typedOther.debugAssertIsValid());
Hixie's avatar
Hixie committed
545 546 547 548
    return minWidth == typedOther.minWidth &&
           maxWidth == typedOther.maxWidth &&
           minHeight == typedOther.minHeight &&
           maxHeight == typedOther.maxHeight;
549
  }
Hixie's avatar
Hixie committed
550

551
  @override
552
  int get hashCode {
553
    assert(debugAssertIsValid());
554
    return hashValues(minWidth, maxWidth, minHeight, maxHeight);
555 556
  }

557
  @override
Hixie's avatar
Hixie committed
558
  String toString() {
559
    final String annotation = isNormalized ? '' : '; NOT NORMALIZED';
560
    if (minWidth == double.infinity && minHeight == double.infinity)
561
      return 'BoxConstraints(biggest$annotation)';
562 563
    if (minWidth == 0 && maxWidth == double.infinity &&
        minHeight == 0 && maxHeight == double.infinity)
564
      return 'BoxConstraints(unconstrained$annotation)';
Hixie's avatar
Hixie committed
565 566 567 568 569 570 571
    String describe(double min, double max, String dim) {
      if (min == max)
        return '$dim=${min.toStringAsFixed(1)}';
      return '${min.toStringAsFixed(1)}<=$dim<=${max.toStringAsFixed(1)}';
    }
    final String width = describe(minWidth, maxWidth, 'w');
    final String height = describe(minHeight, maxHeight, 'h');
572
    return 'BoxConstraints($width, $height$annotation)';
Hixie's avatar
Hixie committed
573
  }
574 575
}

576
/// A hit test entry used by [RenderBox].
577
class BoxHitTestEntry extends HitTestEntry {
578 579 580
  /// Creates a box hit test entry.
  ///
  /// The [localPosition] argument must not be null.
581 582 583
  const BoxHitTestEntry(RenderBox target, this.localPosition)
      : assert(localPosition != null),
        super(target);
Ian Hickson's avatar
Ian Hickson committed
584

585
  @override
Ian Hickson's avatar
Ian Hickson committed
586
  RenderBox get target => super.target;
Adam Barth's avatar
Adam Barth committed
587

588
  /// The position of the hit test in the local coordinates of [target].
589
  final Offset localPosition;
Ian Hickson's avatar
Ian Hickson committed
590

591
  @override
592
  String toString() => '${describeIdentity(target)}@$localPosition';
593 594
}

595
/// Parent data used by [RenderBox] and its subclasses.
596
class BoxParentData extends ParentData {
597
  /// The offset at which to paint the child in the parent's coordinate system.
598
  Offset offset = Offset.zero;
599

600
  @override
601
  String toString() => 'offset=$offset';
602 603
}

Hixie's avatar
Hixie committed
604 605
/// Abstract ParentData subclass for RenderBox subclasses that want the
/// ContainerRenderObjectMixin.
606 607 608 609
///
/// This is a convenience class that mixes in the relevant classes with
/// the relevant type arguments.
abstract class ContainerBoxParentData<ChildType extends RenderObject> extends BoxParentData with ContainerParentDataMixin<ChildType> { }
Hixie's avatar
Hixie committed
610

611
enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight }
612 613

@immutable
614
class _IntrinsicDimensionsCacheEntry {
615
  const _IntrinsicDimensionsCacheEntry(this.dimension, this.argument);
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632

  final _IntrinsicDimension dimension;
  final double argument;

  @override
  bool operator ==(dynamic other) {
    if (other is! _IntrinsicDimensionsCacheEntry)
      return false;
    final _IntrinsicDimensionsCacheEntry typedOther = other;
    return dimension == typedOther.dimension &&
           argument == typedOther.argument;
  }

  @override
  int get hashCode => hashValues(dimension, argument);
}

633
/// A render object in a 2D Cartesian coordinate system.
Adam Barth's avatar
Adam Barth committed
634
///
635 636 637 638 639
/// The [size] of each box is expressed as a width and a height. Each box has
/// its own coordinate system in which its upper left corner is placed at (0,
/// 0). The lower right corner of the box is therefore at (width, height). The
/// box contains all the points including the upper left corner and extending
/// to, but not including, the lower right corner.
Adam Barth's avatar
Adam Barth committed
640 641
///
/// Box layout is performed by passing a [BoxConstraints] object down the tree.
642 643
/// The box constraints establish a min and max value for the child's width and
/// height. In determining its size, the child must respect the constraints
Adam Barth's avatar
Adam Barth committed
644 645 646
/// given to it by its parent.
///
/// This protocol is sufficient for expressing a number of common box layout
647
/// data flows. For example, to implement a width-in-height-out data flow, call
Adam Barth's avatar
Adam Barth committed
648 649 650
/// your child's [layout] function with a set of box constraints with a tight
/// width value (and pass true for parentUsesSize). After the child determines
/// its height, use the child's height to determine your size.
651 652 653 654 655
///
/// ## Writing a RenderBox subclass
///
/// One would implement a new [RenderBox] subclass to describe a new layout
/// model, new paint model, new hit-testing model, or new semantics model, while
656
/// remaining in the Cartesian space defined by the [RenderBox] protocol.
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
///
/// To create a new protocol, consider subclassing [RenderObject] instead.
///
/// ### Constructors and properties of a new RenderBox subclass
///
/// The constructor will typically take a named argument for each property of
/// the class. The value is then passed to a private field of the class and the
/// constructor asserts its correctness (e.g. if it should not be null, it
/// asserts it's not null).
///
/// Properties have the form of a getter/setter/field group like the following:
///
/// ```dart
/// AxisDirection get axis => _axis;
/// AxisDirection _axis;
/// set axis(AxisDirection value) {
///   assert(value != null); // same check as in the constructor
///   if (value == _axis)
///     return;
///   _axis = value;
///   markNeedsLayout();
/// }
/// ```
///
/// The setter will typically finish with either a call to [markNeedsLayout], if
/// the layout uses this property, or [markNeedsPaint], if only the painter
/// function does. (No need to call both, [markNeedsLayout] implies
/// [markNeedsPaint].)
///
/// Consider layout and paint to be expensive; be conservative about calling
/// [markNeedsLayout] or [markNeedsPaint]. They should only be called if the
/// layout (or paint, respectively) has actually changed.
///
/// ### Children
///
/// If a render object is a leaf, that is, it cannot have any children, then
/// ignore this section. (Examples of leaf render objects are [RenderImage] and
/// [RenderParagraph].)
///
/// For render objects with children, there are four possible scenarios:
///
/// * A single [RenderBox] child. In this scenario, consider inheriting from
///   [RenderProxyBox] (if the render object sizes itself to match the child) or
///   [RenderShiftedBox] (if the child will be smaller than the box and the box
///   will align the child inside itself).
///
/// * A single child, but it isn't a [RenderBox]. Use the
///   [RenderObjectWithChildMixin] mixin.
///
/// * A single list of children. Use the [ContainerRenderObjectMixin] mixin.
///
/// * A more complicated child model.
///
/// #### Using RenderProxyBox
///
/// By default, a [RenderProxyBox] render object sizes itself to fit its child, or
/// to be as small as possible if there is no child; it passes all hit testing
/// and painting on to the child, and intrinsic dimensions and baseline
/// measurements similarly are proxied to the child.
///
/// A subclass of [RenderProxyBox] just needs to override the parts of the
/// [RenderBox] protocol that matter. For example, [RenderOpacity] just
/// overrides the paint method (and [alwaysNeedsCompositing] to reflect what the
/// paint method does, and the [visitChildrenForSemantics] method so that the
/// child is hidden from accessibility tools when it's invisible), and adds an
/// [RenderOpacity.opacity] field.
///
/// [RenderProxyBox] assumes that the child is the size of the parent and
/// positioned at 0,0. If this is not true, then use [RenderShiftedBox] instead.
///
/// See
/// [proxy_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/proxy_box.dart)
/// for examples of inheriting from [RenderProxyBox].
///
/// #### Using RenderShiftedBox
///
/// By default, a [RenderShiftedBox] acts much like a [RenderProxyBox] but
/// without assuming that the child is positioned at 0,0 (the actual position
/// recorded in the child's [parentData] field is used), and without providing a
/// default layout algorithm.
///
/// See
/// [shifted_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/shifted_box.dart)
/// for examples of inheriting from [RenderShiftedBox].
///
/// #### Kinds of children and child-specific data
///
/// A [RenderBox] doesn't have to have [RenderBox] children. One can use another
/// subclass of [RenderObject] for a [RenderBox]'s children. See the discussion
/// at [RenderObject].
///
/// Children can have additional data owned by the parent but stored on the
/// child using the [parentData] field. The class used for that data must
/// inherit from [ParentData]. The [setupParentData] method is used to
751
/// initialize the [parentData] field of a child when the child is attached.
752 753 754 755 756 757 758 759 760 761 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 789 790 791 792
///
/// By convention, [RenderBox] objects that have [RenderBox] children use the
/// [BoxParentData] class, which has a [BoxParentData.offset] field to store the
/// position of the child relative to the parent. ([RenderProxyBox] does not
/// need this offset and therefore is an exception to this rule.)
///
/// #### Using RenderObjectWithChildMixin
///
/// If a render object has a single child but it isn't a [RenderBox], then the
/// [RenderObjectWithChildMixin] class, which is a mixin that will handle the
/// boilerplate of managing a child, will be useful.
///
/// It's a generic class with one type argument, the type of the child. For
/// example, if you are building a `RenderFoo` class which takes a single
/// `RenderBar` child, you would use the mixin as follows:
///
/// ```dart
/// class RenderFoo extends RenderBox
///   with RenderObjectWithChildMixin<RenderBar> {
///   // ...
/// }
/// ```
///
/// Since the `RenderFoo` class itself is still a [RenderBox] in this case, you
/// still have to implement the [RenderBox] layout algorithm, as well as
/// features like intrinsics and baselines, painting, and hit testing.
///
/// #### Using ContainerRenderObjectMixin
///
/// If a render box can have multiple children, then the
/// [ContainerRenderObjectMixin] mixin can be used to handle the boilerplate. It
/// uses a linked list to model the children in a manner that is easy to mutate
/// dynamically and that can be walked efficiently. Random access is not
/// efficient in this model; if you need random access to the children consider
/// the next section on more complicated child models.
///
/// The [ContainerRenderObjectMixin] class has two type arguments. The first is
/// the type of the child objects. The second is the type for their
/// [parentData]. The class used for [parentData] must itself have the
/// [ContainerParentDataMixin] class mixed into it; this is where
/// [ContainerRenderObjectMixin] stores the linked list. A [ParentData] class
793
/// can extend [ContainerBoxParentData]; this is essentially
794 795 796 797 798
/// [BoxParentData] mixed with [ContainerParentDataMixin]. For example, if a
/// `RenderFoo` class wanted to have a linked list of [RenderBox] children, one
/// might create a `FooParentData` class as follows:
///
/// ```dart
799
/// class FooParentData extends ContainerBoxParentData<RenderBox> {
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
///   // (any fields you might need for these children)
/// }
/// ```
///
/// When using [ContainerRenderObjectMixin] in a [RenderBox], consider mixing in
/// [RenderBoxContainerDefaultsMixin], which provides a collection of utility
/// methods that implement common parts of the [RenderBox] protocol (such as
/// painting the children).
///
/// The declaration of the `RenderFoo` class itself would thus look like this:
///
/// ```dart
/// class RenderFlex extends RenderBox with
///   ContainerRenderObjectMixin<RenderBox, FooParentData>,
///   RenderBoxContainerDefaultsMixin<RenderBox, FooParentData> {
///   // ...
/// }
/// ```
///
/// When walking the children (e.g. during layout), the following pattern is
/// commonly used (in this case assuming that the children are all [RenderBox]
/// objects and that this render object uses `FooParentData` objects for its
/// children's [parentData] fields):
///
/// ```dart
/// RenderBox child = firstChild;
/// while (child != null) {
///   final FooParentData childParentData = child.parentData;
///   // ...operate on child and childParentData...
///   assert(child.parentData == childParentData);
///   child = childParentData.nextSibling;
/// }
/// ```
///
/// #### More complicated child models
///
/// Render objects can have more complicated models, for example a map of
/// children keyed on an enum, or a 2D grid of efficiently randomly-accessible
/// children, or multiple lists of children, etc. If a render object has a model
/// that can't be handled by the mixins above, it must implement the
/// [RenderObject] child protocol, as follows:
///
/// * Any time a child is removed, call [dropChild] with the child.
///
/// * Any time a child is added, call [adoptChild] with the child.
///
/// * Implement the [attach] method such that it calls [attach] on each child.
///
/// * Implement the [detach] method such that it calls [detach] on each child.
///
/// * Implement the [redepthChildren] method such that it calls [redepthChild]
///   on each child.
///
/// * Implement the [visitChildren] method such that it calls its argument for
///   each child, typically in paint order (back-most to front-most).
///
856
/// * Implement [debugDescribeChildren] such that it outputs a [DiagnosticsNode]
857
///   for each child.
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
///
/// Implementing these seven bullet points is essentially all that the two
/// aforementioned mixins do.
///
/// ### Layout
///
/// [RenderBox] classes implement a layout algorithm. They have a set of
/// constraints provided to them, and they size themselves based on those
/// constraints and whatever other inputs they may have (for example, their
/// children or properties).
///
/// When implementing a [RenderBox] subclass, one must make a choice. Does it
/// size itself exclusively based on the constraints, or does it use any other
/// information in sizing itself? An example of sizing purely based on the
/// constraints would be growing to fit the parent.
///
/// Sizing purely based on the constraints allows the system to make some
875
/// significant optimizations. Classes that use this approach should override
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
/// [sizedByParent] to return true, and then override [performResize] to set the
/// [size] using nothing but the constraints, e.g.:
///
/// ```dart
/// @override
/// bool get sizedByParent => true;
///
/// @override
/// void performResize() {
///   size = constraints.smallest;
/// }
/// ```
///
/// Otherwise, the size is set in the [performLayout] function.
///
/// The [performLayout] function is where render boxes decide, if they are not
/// [sizedByParent], what [size] they should be, and also where they decide
/// where their children should be.
///
/// #### Layout of RenderBox children
///
/// The [performLayout] function should call the [layout] function of each (box)
/// child, passing it a [BoxConstraints] object describing the constraints
/// within which the child can render. Passing tight constraints (see
/// [BoxConstraints.isTight]) to the child will allow the rendering library to
901
/// apply some optimizations, as it knows that if the constraints are tight, the
902 903 904 905 906 907 908 909 910
/// child's dimensions cannot change even if the layout of the child itself
/// changes.
///
/// If the [performLayout] function will use the child's size to affect other
/// aspects of the layout, for example if the render box sizes itself around the
/// child, or positions several children based on the size of those children,
/// then it must specify the `parentUsesSize` argument to the child's [layout]
/// function, setting it to true.
///
911
/// This flag turns off some optimizations; algorithms that do not rely on the
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928
/// children's sizes will be more efficient. (In particular, relying on the
/// child's [size] means that if the child is marked dirty for layout, the
/// parent will probably also be marked dirty for layout, unless the
/// [constraints] given by the parent to the child were tight constraints.)
///
/// For [RenderBox] classes that do not inherit from [RenderProxyBox], once they
/// have laid out their children, should also position them, by setting the
/// [BoxParentData.offset] field of each child's [parentData] object.
///
/// #### Layout of non-RenderBox children
///
/// The children of a [RenderBox] do not have to be [RenderBox]es themselves. If
/// they use another protocol (as discussed at [RenderObject]), then instead of
/// [BoxConstraints], the parent would pass in the appropriate [Constraints]
/// subclass, and instead of reading the child's size, the parent would read
/// whatever the output of [layout] is for that layout protocol. The
/// `parentUsesSize` flag is still used to indicate whether the parent is going
929
/// to read that output, and optimizations still kick in if the child has tight
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
/// constraints (as defined by [Constraints.isTight]).
///
/// ### Painting
///
/// To describe how a render box paints, implement the [paint] method. It is
/// given a [PaintingContext] object and an [Offset]. The painting context
/// provides methods to affect the layer tree as well as a
/// [PaintingContext.canvas] which can be used to add drawing commands. The
/// canvas object should not be cached across calls to the [PaintingContext]'s
/// methods; every time a method on [PaintingContext] is called, there is a
/// chance that the canvas will change identity. The offset specifies the
/// position of the top left corner of the box in the coordinate system of the
/// [PaintingContext.canvas].
///
/// To draw text on a canvas, use a [TextPainter].
///
/// To draw an image to a canvas, use the [paintImage] method.
///
/// A [RenderBox] that uses methods on [PaintingContext] that introduce new
/// layers should override the [alwaysNeedsCompositing] getter and set it to
/// true. If the object sometimes does and sometimes does not, it can have that
/// getter return true in some cases and false in others. In that case, whenever
/// the return value would change, call [markNeedsCompositingBitsUpdate]. (This
/// is done automatically when a child is added or removed, so you don't have to
/// call it explicitly if the [alwaysNeedsCompositing] getter only changes value
/// based on the presence or absence of children.)
///
/// Anytime anything changes on the object that would cause the [paint] method
/// to paint something different (but would not cause the layout to change),
/// the object should call [markNeedsPaint].
///
/// #### Painting children
///
/// The [paint] method's `context` argument has a [PaintingContext.paintChild]
/// method, which should be called for each child that is to be painted. It
/// should be given a reference to the child, and an [Offset] giving the
/// position of the child relative to the parent.
///
/// If the [paint] method applies a transform to the painting context before
/// painting children (or generally applies an additional offset beyond the
/// offset it was itself given as an argument), then the [applyPaintTransform]
/// method should also be overridden. That method must adjust the matrix that it
/// is given in the same manner as it transformed the painting context and
/// offset before painting the given child. This is used by the [globalToLocal]
/// and [localToGlobal] methods.
///
/// #### Hit Tests
///
/// Hit testing for render boxes is implemented by the [hitTest] method. The
/// default implementation of this method defers to [hitTestSelf] and
/// [hitTestChildren]. When implementing hit testing, you can either override
/// these latter two methods, or ignore them and just override [hitTest].
///
983
/// The [hitTest] method itself is given an [Offset], and must return true if the
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
/// object or one of its children has absorbed the hit (preventing objects below
/// this one from being hit), or false if the hit can continue to other objects
/// below this one.
///
/// For each child [RenderBox], the [hitTest] method on the child should be
/// called with the same [HitTestResult] argument and with the point transformed
/// into the child's coordinate space (in the same manner that the
/// [applyPaintTransform] method would). The default implementation defers to
/// [hitTestChildren] to call the children. [RenderBoxContainerDefaultsMixin]
/// provides a [RenderBoxContainerDefaultsMixin.defaultHitTestChildren] method
/// that does this assuming that the children are axis-aligned, not transformed,
/// and positioned according to the [BoxParentData.offset] field of the
/// [parentData]; more elaborate boxes can override [hitTestChildren]
/// accordingly.
///
/// If the object is hit, then it should also add itself to the [HitTestResult]
/// object that is given as an argument to the [hitTest] method, using
/// [HitTestResult.add]. The default implementation defers to [hitTestSelf] to
/// determine if the box is hit. If the object adds itself before the children
/// can add themselves, then it will be as if the object was above the children.
/// If it adds itself after the children, then it will be as if it was below the
/// children. Entries added to the [HitTestResult] object should use the
/// [BoxHitTestEntry] class. The entries are subsequently walked by the system
/// in the order they were added, and for each entry, the target's [handleEvent]
/// method is called, passing in the [HitTestEntry] object.
///
/// Hit testing cannot rely on painting having happened.
///
/// ### Semantics
///
/// For a render box to be accessible, implement the
/// [describeApproximatePaintClip] and [visitChildrenForSemantics] methods, and
1016
/// the [semanticsAnnotator] getter. The default implementations are sufficient
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
/// for objects that only affect layout, but nodes that represent interactive
/// components or information (diagrams, text, images, etc) should provide more
/// complete implementations. For more information, see the documentation for
/// these members.
///
/// ### Intrinsics and Baselines
///
/// The layout, painting, hit testing, and semantics protocols are common to all
/// render objects. [RenderBox] objects must implement two additional protocols:
/// intrinsic sizing and baseline measurements.
///
/// There are four methods to implement for intrinsic sizing, to compute the
/// minimum and maximum intrinsic width and height of the box. The documentation
/// for these methods discusses the protocol in detail:
/// [computeMinIntrinsicWidth], [computeMaxIntrinsicWidth],
/// [computeMinIntrinsicHeight], [computeMaxIntrinsicHeight].
///
/// In addition, if the box has any children, it must implement
/// [computeDistanceToActualBaseline]. [RenderProxyBox] provides a simple
/// implementation that forwards to the child; [RenderShiftedBox] provides an
/// implementation that offsets the child's baseline information by the position
/// of the child relative to the parent. If you do not inherited from either of
/// these classes, however, you must implement the algorithm yourself.
1040
abstract class RenderBox extends RenderObject {
1041
  @override
1042
  void setupParentData(covariant RenderObject child) {
1043 1044 1045 1046
    if (child.parentData is! BoxParentData)
      child.parentData = new BoxParentData();
  }

1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
  Map<_IntrinsicDimensionsCacheEntry, double> _cachedIntrinsicDimensions;

  double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double computer(double argument)) {
    assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
    bool shouldCache = true;
    assert(() {
      // we don't want the checked-mode intrinsic tests to affect
      // who gets marked dirty, etc.
      if (RenderObject.debugCheckingIntrinsics)
        shouldCache = false;
      return true;
1058
    }());
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
    if (shouldCache) {
      _cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
      return _cachedIntrinsicDimensions.putIfAbsent(
        new _IntrinsicDimensionsCacheEntry(dimension, argument),
        () => computer(argument)
      );
    }
    return computer(argument);
  }

1069 1070 1071 1072 1073 1074 1075
  /// Returns the minimum width that this box could be without failing to
  /// correctly paint its contents within itself, without clipping.
  ///
  /// The height argument may give a specific height to assume. The given height
  /// can be infinite, meaning that the intrinsic width in an unconstrained
  /// environment is being requested. The given height should never be negative
  /// or null.
Adam Barth's avatar
Adam Barth committed
1076
  ///
1077 1078 1079 1080 1081 1082 1083
  /// This function should only be called on one's children. Calling this
  /// function couples the child with the parent so that when the child's layout
  /// changes, the parent is notified (via [markNeedsLayout]).
  ///
  /// Calling this function is expensive and as it can result in O(N^2)
  /// behavior.
  ///
1084
  /// Do not override this method. Instead, implement [computeMinIntrinsicWidth].
1085 1086
  @mustCallSuper
  double getMinIntrinsicWidth(double height) {
1087 1088 1089 1090 1091
    assert(() {
      if (height == null) {
        throw new FlutterError(
          'The height argument to getMinIntrinsicWidth was null.\n'
          'The argument to getMinIntrinsicWidth must not be negative or null. '
1092
          'If you do not have a specific height in mind, then pass double.infinity instead.'
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
        );
      }
      if (height < 0.0) {
        throw new FlutterError(
          'The height argument to getMinIntrinsicWidth was negative.\n'
          'The argument to getMinIntrinsicWidth must not be negative or null. '
          'If you perform computations on another height before passing it to '
          'getMinIntrinsicWidth, consider using math.max() or double.clamp() '
          'to force the value into the valid range.'
        );
      }
      return true;
1105
    }());
1106 1107 1108 1109 1110 1111 1112 1113 1114
    return _computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth);
  }

  /// Computes the value returned by [getMinIntrinsicWidth]. Do not call this
  /// function directly, instead, call [getMinIntrinsicWidth].
  ///
  /// Override in subclasses that implement [performLayout]. This method should
  /// return the minimum width that this box could be without failing to
  /// correctly paint its contents within itself, without clipping.
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
  ///
  /// If the layout algorithm is independent of the context (e.g. it always
  /// tries to be a particular size), or if the layout algorithm is
  /// width-in-height-out, or if the layout algorithm uses both the incoming
  /// width and height constraints (e.g. it always sizes itself to
  /// [BoxConstraints.biggest]), then the `height` argument should be ignored.
  ///
  /// If the layout algorithm is strictly height-in-width-out, or is
  /// height-in-width-out when the width is unconstrained, then the height
  /// argument is the height to use.
  ///
1126 1127
  /// The `height` argument will never be negative or null. It may be infinite.
  ///
1128 1129 1130 1131
  /// If this algorithm depends on the intrinsic dimensions of a child, the
  /// intrinsic dimensions of that child should be obtained using the functions
  /// whose names start with `get`, not `compute`.
  ///
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
  /// This function should never return a negative or infinite value.
  ///
  /// ## Examples
  ///
  /// ### Text
  ///
  /// Text is the canonical example of a width-in-height-out algorithm. The
  /// `height` argument is therefore ignored.
  ///
  /// Consider the string "Hello World" The _maximum_ intrinsic width (as
1142
  /// returned from [computeMaxIntrinsicWidth]) would be the width of the string
1143 1144 1145 1146 1147 1148
  /// with no line breaks.
  ///
  /// The minimum intrinsic width would be the width of the widest word, "Hello"
  /// or "World". If the text is rendered in an even narrower width, however, it
  /// might still not overflow. For example, maybe the rendering would put a
  /// line-break half-way through the words, as in "Hel⁞lo⁞Wor⁞ld". However,
1149
  /// this wouldn't be a _correct_ rendering, and [computeMinIntrinsicWidth] is
1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
  /// supposed to render the minimum width that the box could be without failing
  /// to _correctly_ paint the contents within itself.
  ///
  /// The minimum intrinsic _height_ for a given width smaller than the minimum
  /// intrinsic width could therefore be greater than the minimum intrinsic
  /// height for the minimum intrinsic width.
  ///
  /// ### Viewports (e.g. scrolling lists)
  ///
  /// Some render boxes are intended to clip their children. For example, the
  /// render box for a scrolling list might always size itself to its parents'
  /// size (or rather, to the maximum incoming constraints), regardless of the
  /// children's sizes, and then clip the children and position them based on
  /// the current scroll offset.
  ///
  /// The intrinsic dimensions in these cases still depend on the children, even
  /// though the layout algorithm sizes the box in a way independent of the
  /// children. It is the size that is needed to paint the box's contents (in
  /// this case, the children) _without clipping_ that matters.
  ///
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
  /// ### When the intrinsic dimensions cannot be known
  ///
  /// There are cases where render objects do not have an efficient way to
  /// compute their intrinsic dimensions. For example, it may be prohibitively
  /// expensive to reify and measure every child of a lazy viewport (viewports
  /// generally only instantiate the actually visible children), or the
  /// dimensions may be computed by a callback about which the render object
  /// cannot reason.
  ///
  /// In such cases, it may be impossible (or at least impractical) to actually
  /// return a valid answer. In such cases, the intrinsic functions should throw
  /// when [RenderObject.debugCheckingIntrinsics] is false and asserts are
  /// enabled, and return 0.0 otherwise.
  ///
  /// See the implementations of [LayoutBuilder] or [RenderViewportBase] for
  /// examples (in particular,
  /// [RenderViewportBase.debugThrowIfNotCheckingIntrinsics]).
1187 1188 1189 1190 1191 1192 1193 1194
  ///
  /// ### Aspect-ratio-driven boxes
  ///
  /// Some boxes always return a fixed size based on the constraints. For these
  /// boxes, the intrinsic functions should return the appropriate size when the
  /// incoming `height` or `width` argument is finite, treating that as a tight
  /// constraint in the respective direction and treating the other direction's
  /// constraints as unbounded. This is because the definitions of
1195 1196 1197
  /// [computeMinIntrinsicWidth] and [computeMinIntrinsicHeight] are in terms of
  /// what the dimensions _could be_, and such boxes can only be one size in
  /// such cases.
1198 1199 1200
  ///
  /// When the incoming argument is not finite, then they should return the
  /// actual intrinsic dimensions based on the contents, as any other box would.
1201 1202
  @protected
  double computeMinIntrinsicWidth(double height) {
1203
    return 0.0;
1204 1205
  }

Adam Barth's avatar
Adam Barth committed
1206
  /// Returns the smallest width beyond which increasing the width never
1207 1208
  /// decreases the preferred height. The preferred height is the value that
  /// would be returned by [getMinIntrinsicHeight] for that width.
Adam Barth's avatar
Adam Barth committed
1209
  ///
1210 1211 1212 1213 1214
  /// The height argument may give a specific height to assume. The given height
  /// can be infinite, meaning that the intrinsic width in an unconstrained
  /// environment is being requested. The given height should never be negative
  /// or null.
  ///
1215 1216 1217 1218 1219 1220 1221
  /// This function should only be called on one's children. Calling this
  /// function couples the child with the parent so that when the child's layout
  /// changes, the parent is notified (via [markNeedsLayout]).
  ///
  /// Calling this function is expensive and as it can result in O(N^2)
  /// behavior.
  ///
1222
  /// Do not override this method. Instead, implement
1223 1224 1225
  /// [computeMaxIntrinsicWidth].
  @mustCallSuper
  double getMaxIntrinsicWidth(double height) {
1226 1227 1228 1229 1230
    assert(() {
      if (height == null) {
        throw new FlutterError(
          'The height argument to getMaxIntrinsicWidth was null.\n'
          'The argument to getMaxIntrinsicWidth must not be negative or null. '
1231
          'If you do not have a specific height in mind, then pass double.infinity instead.'
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
        );
      }
      if (height < 0.0) {
        throw new FlutterError(
          'The height argument to getMaxIntrinsicWidth was negative.\n'
          'The argument to getMaxIntrinsicWidth must not be negative or null. '
          'If you perform computations on another height before passing it to '
          'getMaxIntrinsicWidth, consider using math.max() or double.clamp() '
          'to force the value into the valid range.'
        );
      }
      return true;
1244
    }());
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
    return _computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth);
  }

  /// Computes the value returned by [getMaxIntrinsicWidth]. Do not call this
  /// function directly, instead, call [getMaxIntrinsicWidth].
  ///
  /// Override in subclasses that implement [performLayout]. This should return
  /// the smallest width beyond which increasing the width never decreases the
  /// preferred height. The preferred height is the value that would be returned
  /// by [computeMinIntrinsicHeight] for that width.
1255 1256 1257
  ///
  /// If the layout algorithm is strictly height-in-width-out, or is
  /// height-in-width-out when the width is unconstrained, then this should
1258
  /// return the same value as [computeMinIntrinsicWidth] for the same height.
1259 1260 1261
  ///
  /// Otherwise, the height argument should be ignored, and the returned value
  /// should be equal to or bigger than the value returned by
1262
  /// [computeMinIntrinsicWidth].
1263
  ///
1264 1265
  /// The `height` argument will never be negative or null. It may be infinite.
  ///
1266 1267 1268 1269 1270
  /// The value returned by this method might not match the size that the object
  /// would actually take. For example, a [RenderBox] subclass that always
  /// exactly sizes itself using [BoxConstraints.biggest] might well size itself
  /// bigger than its max intrinsic size.
  ///
1271 1272 1273 1274
  /// If this algorithm depends on the intrinsic dimensions of a child, the
  /// intrinsic dimensions of that child should be obtained using the functions
  /// whose names start with `get`, not `compute`.
  ///
1275 1276
  /// This function should never return a negative or infinite value.
  ///
1277 1278 1279
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
  double computeMaxIntrinsicWidth(double height) {
1280
    return 0.0;
1281 1282
  }

1283 1284 1285 1286 1287 1288 1289
  /// Returns the minimum height that this box could be without failing to
  /// correctly paint its contents within itself, without clipping.
  ///
  /// The width argument may give a specific width to assume. The given width
  /// can be infinite, meaning that the intrinsic height in an unconstrained
  /// environment is being requested. The given width should never be negative
  /// or null.
Adam Barth's avatar
Adam Barth committed
1290
  ///
1291 1292 1293 1294 1295 1296 1297
  /// This function should only be called on one's children. Calling this
  /// function couples the child with the parent so that when the child's layout
  /// changes, the parent is notified (via [markNeedsLayout]).
  ///
  /// Calling this function is expensive and as it can result in O(N^2)
  /// behavior.
  ///
1298
  /// Do not override this method. Instead, implement
1299 1300 1301
  /// [computeMinIntrinsicHeight].
  @mustCallSuper
  double getMinIntrinsicHeight(double width) {
1302 1303 1304 1305 1306
    assert(() {
      if (width == null) {
        throw new FlutterError(
          'The width argument to getMinIntrinsicHeight was null.\n'
          'The argument to getMinIntrinsicHeight must not be negative or null. '
1307
          'If you do not have a specific width in mind, then pass double.infinity instead.'
1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319
        );
      }
      if (width < 0.0) {
        throw new FlutterError(
          'The width argument to getMinIntrinsicHeight was negative.\n'
          'The argument to getMinIntrinsicHeight must not be negative or null. '
          'If you perform computations on another width before passing it to '
          'getMinIntrinsicHeight, consider using math.max() or double.clamp() '
          'to force the value into the valid range.'
        );
      }
      return true;
1320
    }());
1321 1322 1323 1324 1325 1326 1327 1328 1329
    return _computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight);
  }

  /// Computes the value returned by [getMinIntrinsicHeight]. Do not call this
  /// function directly, instead, call [getMinIntrinsicHeight].
  ///
  /// Override in subclasses that implement [performLayout]. Should return the
  /// minimum height that this box could be without failing to correctly paint
  /// its contents within itself, without clipping.
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
  ///
  /// If the layout algorithm is independent of the context (e.g. it always
  /// tries to be a particular size), or if the layout algorithm is
  /// height-in-width-out, or if the layout algorithm uses both the incoming
  /// height and width constraints (e.g. it always sizes itself to
  /// [BoxConstraints.biggest]), then the `width` argument should be ignored.
  ///
  /// If the layout algorithm is strictly width-in-height-out, or is
  /// width-in-height-out when the height is unconstrained, then the width
  /// argument is the width to use.
  ///
1341 1342
  /// The `width` argument will never be negative or null. It may be infinite.
  ///
1343 1344 1345 1346
  /// If this algorithm depends on the intrinsic dimensions of a child, the
  /// intrinsic dimensions of that child should be obtained using the functions
  /// whose names start with `get`, not `compute`.
  ///
1347 1348
  /// This function should never return a negative or infinite value.
  ///
1349 1350
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
1351
  double computeMinIntrinsicHeight(double width) {
1352
    return 0.0;
1353 1354
  }

Adam Barth's avatar
Adam Barth committed
1355
  /// Returns the smallest height beyond which increasing the height never
1356 1357
  /// decreases the preferred width. The preferred width is the value that
  /// would be returned by [getMinIntrinsicWidth] for that height.
Adam Barth's avatar
Adam Barth committed
1358
  ///
1359 1360 1361 1362 1363
  /// The width argument may give a specific width to assume. The given width
  /// can be infinite, meaning that the intrinsic height in an unconstrained
  /// environment is being requested. The given width should never be negative
  /// or null.
  ///
1364 1365 1366 1367 1368 1369 1370
  /// This function should only be called on one's children. Calling this
  /// function couples the child with the parent so that when the child's layout
  /// changes, the parent is notified (via [markNeedsLayout]).
  ///
  /// Calling this function is expensive and as it can result in O(N^2)
  /// behavior.
  ///
1371
  /// Do not override this method. Instead, implement
1372 1373 1374
  /// [computeMaxIntrinsicHeight].
  @mustCallSuper
  double getMaxIntrinsicHeight(double width) {
1375 1376 1377 1378 1379
    assert(() {
      if (width == null) {
        throw new FlutterError(
          'The width argument to getMaxIntrinsicHeight was null.\n'
          'The argument to getMaxIntrinsicHeight must not be negative or null. '
1380
          'If you do not have a specific width in mind, then pass double.infinity instead.'
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392
        );
      }
      if (width < 0.0) {
        throw new FlutterError(
          'The width argument to getMaxIntrinsicHeight was negative.\n'
          'The argument to getMaxIntrinsicHeight must not be negative or null. '
          'If you perform computations on another width before passing it to '
          'getMaxIntrinsicHeight, consider using math.max() or double.clamp() '
          'to force the value into the valid range.'
        );
      }
      return true;
1393
    }());
1394 1395 1396 1397 1398 1399 1400 1401 1402 1403
    return _computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight);
  }

  /// Computes the value returned by [getMaxIntrinsicHeight]. Do not call this
  /// function directly, instead, call [getMaxIntrinsicHeight].
  ///
  /// Override in subclasses that implement [performLayout]. Should return the
  /// smallest height beyond which increasing the height never decreases the
  /// preferred width. The preferred width is the value that would be returned
  /// by [computeMinIntrinsicWidth] for that height.
1404 1405 1406
  ///
  /// If the layout algorithm is strictly width-in-height-out, or is
  /// width-in-height-out when the height is unconstrained, then this should
1407
  /// return the same value as [computeMinIntrinsicHeight] for the same width.
1408 1409 1410
  ///
  /// Otherwise, the width argument should be ignored, and the returned value
  /// should be equal to or bigger than the value returned by
1411
  /// [computeMinIntrinsicHeight].
1412
  ///
1413 1414
  /// The `width` argument will never be negative or null. It may be infinite.
  ///
1415 1416 1417 1418 1419
  /// The value returned by this method might not match the size that the object
  /// would actually take. For example, a [RenderBox] subclass that always
  /// exactly sizes itself using [BoxConstraints.biggest] might well size itself
  /// bigger than its max intrinsic size.
  ///
1420 1421 1422 1423
  /// If this algorithm depends on the intrinsic dimensions of a child, the
  /// intrinsic dimensions of that child should be obtained using the functions
  /// whose names start with `get`, not `compute`.
  ///
1424 1425
  /// This function should never return a negative or infinite value.
  ///
1426 1427
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
1428
  double computeMaxIntrinsicHeight(double width) {
1429
    return 0.0;
1430 1431
  }

1432 1433 1434
  /// Whether this render object has undergone layout and has a [size].
  bool get hasSize => _size != null;

1435
  /// The size of this render box computed during layout.
1436 1437 1438 1439 1440 1441 1442
  ///
  /// This value is stale whenever this object is marked as needing layout.
  /// During [performLayout], do not read the size of a child unless you pass
  /// true for parentUsesSize when calling the child's [layout] function.
  ///
  /// The size of a box should be set only during the box's [performLayout] or
  /// [performResize] functions. If you wish to change the size of a box outside
1443
  /// of those functions, call [markNeedsLayout] instead to schedule a layout of
1444 1445
  /// the box.
  Size get size {
1446
    assert(hasSize, 'RenderBox was not laid out: ${toString()}');
1447 1448
    assert(() {
      if (_size is _DebugSize) {
Hixie's avatar
Hixie committed
1449
        final _DebugSize _size = this._size;
1450 1451
        assert(_size._owner == this);
        if (RenderObject.debugActiveLayout != null) {
1452 1453 1454 1455 1456
          // We are always allowed to access our own size (for print debugging
          // and asserts if nothing else). Other than us, the only object that's
          // allowed to read our size is our parent, if they've said they will.
          // If you hit this assert trying to access a child's size, pass
          // "parentUsesSize: true" to that child's layout().
1457 1458 1459
          assert(debugDoingThisResize || debugDoingThisLayout ||
                 (RenderObject.debugActiveLayout == parent && _size._canBeUsedByParent));
        }
Hixie's avatar
Hixie committed
1460
        assert(_size == this._size);
1461 1462
      }
      return true;
1463
    }());
1464 1465 1466
    return _size;
  }
  Size _size;
1467
  @protected
1468 1469 1470
  /// Setting the size, in checked mode, triggers some analysis of the render box,
  /// as implemented by [debugAssertDoesMeetConstraints], including calling the intrinsic
  /// sizing methods and checking that they meet certain invariants.
1471
  set size(Size value) {
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500
    assert(!(debugDoingThisResize && debugDoingThisLayout));
    assert(sizedByParent || !debugDoingThisResize);
    assert(() {
      if ((sizedByParent && debugDoingThisResize) ||
          (!sizedByParent && debugDoingThisLayout))
        return true;
      assert(!debugDoingThisResize);
      String contract, violation, hint;
      if (debugDoingThisLayout) {
        assert(sizedByParent);
        violation = 'It appears that the size setter was called from performLayout().';
        hint = '';
      } else {
        violation = 'The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).';
        if (owner != null && owner.debugDoingLayout)
          hint = 'Only the object itself can set its size. It is a contract violation for other objects to set it.';
      }
      if (sizedByParent)
        contract = 'Because this RenderBox has sizedByParent set to true, it must set its size in performResize().';
      else
        contract = 'Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().';
      throw new FlutterError(
        'RenderBox size setter called incorrectly.\n'
        '$violation\n'
        '$hint\n'
        '$contract\n'
        'The RenderBox in question is:\n'
        '  $this'
      );
1501
    }());
1502
    assert(() {
1503
      value = debugAdoptSize(value);
1504
      return true;
1505
    }());
1506
    _size = value;
1507
    assert(() { debugAssertDoesMeetConstraints(); return true; }());
1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524
  }

  /// Claims ownership of the given [Size].
  ///
  /// In debug mode, the [RenderBox] class verifies that [Size] objects obtained
  /// from other [RenderBox] objects are only used according to the semantics of
  /// the [RenderBox] protocol, namely that a [Size] from a [RenderBox] can only
  /// be used by its parent, and then only if `parentUsesSize` was set.
  ///
  /// Sometimes, a [Size] that can validly be used ends up no longer being valid
  /// over time. The common example is a [Size] taken from a child that is later
  /// removed from the parent. In such cases, this method can be called to first
  /// check whether the size can legitimately be used, and if so, to then create
  /// a new [Size] that can be used going forward, regardless of what happens to
  /// the original owner.
  Size debugAdoptSize(Size value) {
    Size result = value;
1525
    assert(() {
1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567
      if (value is _DebugSize) {
        if (value._owner != this) {
          if (value._owner.parent != this) {
            throw new FlutterError(
              'The size property was assigned a size inappropriately.\n'
              'The following render object:\n'
              '  $this\n'
              '...was assigned a size obtained from:\n'
              '  ${value._owner}\n'
              'However, this second render object is not, or is no longer, a '
              'child of the first, and it is therefore a violation of the '
              'RenderBox layout protocol to use that size in the layout of the '
              'first render object.\n'
              'If the size was obtained at a time where it was valid to read '
              'the size (because the second render object above was a child '
              'of the first at the time), then it should be adopted using '
              'debugAdoptSize at that time.\n'
              'If the size comes from a grandchild or a render object from an '
              'entirely different part of the render tree, then there is no '
              'way to be notified when the size changes and therefore attempts '
              'to read that size are almost certainly a source of bugs. A different '
              'approach should be used.'
            );
          }
          if (!value._canBeUsedByParent) {
            throw new FlutterError(
              'A child\'s size was used without setting parentUsesSize.\n'
              'The following render object:\n'
              '  $this\n'
              '...was assigned a size obtained from its child:\n'
              '  ${value._owner}\n'
              'However, when the child was laid out, the parentUsesSize argument '
              'was not set or set to false. Subsequently this transpired to be '
              'inaccurate: the size was nonetheless used by the parent.\n'
              'It is important to tell the framework if the size will be used or not '
              'as several important performance optimizations can be made if the '
              'size will not be used by the parent.'
            );
          }
        }
      }
      result = new _DebugSize(value, this, debugCanParentUseSize);
1568
      return true;
1569
    }());
1570
    return result;
1571 1572
  }

1573
  @override
1574
  Rect get semanticBounds => Offset.zero & size;
Hixie's avatar
Hixie committed
1575

1576
  @override
1577 1578 1579 1580 1581
  void debugResetSize() {
    // updates the value of size._canBeUsedByParent if necessary
    size = size;
  }

1582 1583 1584 1585 1586 1587
  Map<TextBaseline, double> _cachedBaselines;
  static bool _debugDoingBaseline = false;
  static bool _debugSetDoingBaseline(bool value) {
    _debugDoingBaseline = value;
    return true;
  }
Adam Barth's avatar
Adam Barth committed
1588 1589 1590 1591 1592 1593 1594 1595

  /// Returns the distance from the y-coordinate of the position of the box to
  /// the y-coordinate of the first given baseline in the box's contents.
  ///
  /// Used by certain layout models to align adjacent boxes on a common
  /// baseline, regardless of padding, font size differences, etc. If there is
  /// no baseline, this function returns the distance from the y-coordinate of
  /// the position of the box to the y-coordinate of the bottom of the box
1596
  /// (i.e., the height of the box) unless the caller passes true
Adam Barth's avatar
Adam Barth committed
1597 1598
  /// for `onlyReal`, in which case the function returns null.
  ///
1599 1600 1601
  /// Only call this function after calling [layout] on this box. You
  /// are only allowed to call this from the parent of this box during
  /// that parent's [performLayout] or [paint] functions.
1602 1603 1604
  ///
  /// When implementing a [RenderBox] subclass, to override the baseline
  /// computation, override [computeDistanceToActualBaseline].
1605
  double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) {
1606
    assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1607
    assert(!debugNeedsLayout);
1608
    assert(() {
1609
      final RenderObject parent = this.parent;
1610
      if (owner.debugDoingLayout)
1611
        return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout;
1612
      if (owner.debugDoingPaint)
1613 1614
        return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
               ((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
1615
      assert(parent == this.parent);
1616
      return false;
1617
    }());
1618
    assert(_debugSetDoingBaseline(true));
1619
    final double result = getDistanceToActualBaseline(baseline);
1620 1621 1622 1623 1624
    assert(_debugSetDoingBaseline(false));
    if (result == null && !onlyReal)
      return size.height;
    return result;
  }
Adam Barth's avatar
Adam Barth committed
1625 1626 1627 1628 1629 1630

  /// Calls [computeDistanceToActualBaseline] and caches the result.
  ///
  /// This function must only be called from [getDistanceToBaseline] and
  /// [computeDistanceToActualBaseline]. Do not call this function directly from
  /// outside those two methods.
1631
  @protected
1632
  @mustCallSuper
1633
  double getDistanceToActualBaseline(TextBaseline baseline) {
1634
    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1635
    _cachedBaselines ??= <TextBaseline, double>{};
1636 1637 1638
    _cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
    return _cachedBaselines[baseline];
  }
Adam Barth's avatar
Adam Barth committed
1639 1640 1641 1642 1643

  /// Returns the distance from the y-coordinate of the position of the box to
  /// the y-coordinate of the first given baseline in the box's contents, if
  /// any, or null otherwise.
  ///
1644 1645 1646
  /// Do not call this function directly. If you need to know the baseline of a
  /// child from an invocation of [performLayout] or [paint], call
  /// [getDistanceToBaseline].
Adam Barth's avatar
Adam Barth committed
1647
  ///
1648
  /// Subclasses should override this method to supply the distances to their
1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663
  /// baselines. When implementing this method, there are generally three
  /// strategies:
  ///
  ///  * For classes that use the [ContainerRenderObjectMixin] child model,
  ///    consider mixing in the [RenderBoxContainerDefaultsMixin] class and
  ///    using
  ///    [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline].
  ///
  ///  * For classes that define a particular baseline themselves, return that
  ///    value directly.
  ///
  ///  * For classes that have a child to which they wish to defer the
  ///    computation, call [getDistanceToActualBaseline] on the child (not
  ///    [computeDistanceToActualBaseline], the internal implementation, and not
  ///    [getDistanceToBaseline], the public entry point for this API).
1664
  @protected
1665
  double computeDistanceToActualBaseline(TextBaseline baseline) {
1666
    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1667 1668 1669
    return null;
  }

1670
  /// The box constraints most recently received from the parent.
1671
  @override
1672
  BoxConstraints get constraints => super.constraints;
1673 1674

  @override
1675
  void debugAssertDoesMeetConstraints() {
1676
    assert(constraints != null);
1677 1678
    assert(() {
      if (!hasSize) {
1679
        assert(!debugNeedsLayout); // this is called in the size= setter during layout, but in that case we have a size
1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691
        String contract;
        if (sizedByParent)
          contract = 'Because this RenderBox has sizedByParent set to true, it must set its size in performResize().\n';
        else
          contract = 'Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().\n';
        throw new FlutterError(
          'RenderBox did not set its size during layout.\n'
          '$contract'
          'It appears that this did not happen; layout completed, but the size property is still null.\n'
          'The RenderBox in question is:\n'
          '  $this'
        );
1692
      }
1693
      // verify that the size is not infinite
1694
      if (!_size.isFinite) {
1695
        final StringBuffer information = new StringBuffer();
1696 1697 1698 1699 1700
        if (!constraints.hasBoundedWidth) {
          RenderBox node = this;
          while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
            node = node.parent;
          information.writeln('The nearest ancestor providing an unbounded width constraint is:');
1701
          information.write('  ');
1702
          information.writeln(node.toStringShallow(joiner: '\n  '));
1703
         }
1704 1705 1706 1707 1708
        if (!constraints.hasBoundedHeight) {
          RenderBox node = this;
          while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
            node = node.parent;
          information.writeln('The nearest ancestor providing an unbounded height constraint is:');
1709
          information.write('  ');
1710
          information.writeln(node.toStringShallow(joiner: '\n  '));
1711

1712 1713 1714 1715 1716 1717 1718
        }
        throw new FlutterError(
          '$runtimeType object was given an infinite size during layout.\n'
          'This probably means that it is a render object that tries to be '
          'as big as possible, but it was put inside another render object '
          'that allows its children to pick their own size.\n'
          '$information'
1719 1720 1721 1722
          'The constraints that applied to the $runtimeType were:\n'
          '  $constraints\n'
          'The exact size it was given was:\n'
          '  $_size\n'
1723 1724
          'See https://flutter.io/layout/ for more information.'
        );
1725
      }
1726 1727 1728 1729 1730 1731 1732 1733 1734 1735
      // verify that the size is within the constraints
      if (!constraints.isSatisfiedBy(_size)) {
        throw new FlutterError(
          '$runtimeType does not meet its constraints.\n'
          'Constraints: $constraints\n'
          'Size: $_size\n'
          'If you are not writing your own RenderBox subclass, then this is not '
          'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
        );
      }
1736
      if (debugCheckIntrinsicSizes) {
1737
        // verify that the intrinsics are sane
1738 1739
        assert(!RenderObject.debugCheckingIntrinsics);
        RenderObject.debugCheckingIntrinsics = true;
1740
        final StringBuffer failures = new StringBuffer();
1741
        int failureCount = 0;
1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753

        double testIntrinsic(double function(double extent), String name, double constraint) {
          final double result = function(constraint);
          if (result < 0) {
            failures.writeln(' * $name($constraint) returned a negative value: $result');
            failureCount += 1;
          }
          if (!result.isFinite) {
            failures.writeln(' * $name($constraint) returned a non-finite value: $result');
            failureCount += 1;
          }
          return result;
1754
        }
1755

1756 1757 1758 1759 1760 1761 1762
        void testIntrinsicsForValues(double getMin(double extent), double getMax(double extent), String name, double constraint) {
          final double min = testIntrinsic(getMin, 'getMinIntrinsic$name', constraint);
          final double max = testIntrinsic(getMax, 'getMaxIntrinsic$name', constraint);
          if (min > max) {
            failures.writeln(' * getMinIntrinsic$name($constraint) returned a larger value ($min) than getMaxIntrinsic$name($constraint) ($max)');
            failureCount += 1;
          }
1763
        }
1764

1765 1766
        testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', double.infinity);
        testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', double.infinity);
1767 1768 1769 1770 1771
        if (constraints.hasBoundedWidth)
          testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', constraints.maxWidth);
        if (constraints.hasBoundedHeight)
          testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', constraints.maxHeight);

1772 1773
        // TODO(ianh): Test that values are internally consistent in more ways than the above.

1774 1775 1776 1777
        RenderObject.debugCheckingIntrinsics = false;
        if (failures.isNotEmpty) {
          assert(failureCount > 0);
          throw new FlutterError(
1778 1779
            'The intrinsic dimension methods of the $runtimeType class returned values that violate the intrinsic protocol contract.\n'
            'The following ${failureCount > 1 ? "failures" : "failure"} was detected:\n'
1780 1781 1782 1783 1784
            '$failures'
            'If you are not writing your own RenderBox subclass, then this is not\n'
            'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
          );
        }
1785 1786
      }
      return true;
1787
    }());
1788 1789
  }

1790
  @override
1791
  void markNeedsLayout() {
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804
    if ((_cachedBaselines != null && _cachedBaselines.isNotEmpty) ||
        (_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions.isNotEmpty)) {
      // If we have cached data, then someone must have used our data.
      // Since the parent will shortly be marked dirty, we can forget that they
      // used the baseline and/or intrinsic dimensions. If they use them again,
      // then we'll fill the cache again, and if we get dirty again, we'll
      // notify them again.
      _cachedBaselines?.clear();
      _cachedIntrinsicDimensions?.clear();
       if (parent is RenderObject) {
        markParentNeedsLayout();
        return;
      }
1805 1806 1807
    }
    super.markNeedsLayout();
  }
1808 1809

  @override
1810
  void performResize() {
1811
    // default behavior for subclasses that have sizedByParent = true
1812
    size = constraints.smallest;
1813
    assert(size.isFinite);
1814
  }
1815 1816

  @override
1817
  void performLayout() {
1818 1819
    assert(() {
      if (!sizedByParent) {
1820 1821 1822 1823 1824 1825
        throw new FlutterError(
          '$runtimeType did not implement performLayout().\n'
          'RenderBox subclasses need to either override performLayout() to '
          'set a size and lay out any children, or, set sizedByParent to true '
          'so that performResize() sizes the render object.'
        );
1826 1827
      }
      return true;
1828
    }());
1829 1830
  }

1831
  /// Determines the set of render objects located at the given position.
Adam Barth's avatar
Adam Barth committed
1832
  ///
1833 1834 1835 1836
  /// Returns true, and adds any render objects that contain the point to the
  /// given hit test result, if this render object or one of its descendants
  /// absorbs the hit (preventing objects below this one from being hit).
  /// Returns false if the hit can continue to other objects below this one.
Adam Barth's avatar
Adam Barth committed
1837
  ///
1838 1839 1840 1841
  /// The caller is responsible for transforming [position] from global
  /// coordinates to its location relative to the origin of this [RenderBox].
  /// This [RenderBox] is responsible for checking whether the given position is
  /// within its bounds.
1842 1843 1844 1845 1846 1847 1848
  ///
  /// Hit testing requires layout to be up-to-date but does not require painting
  /// to be up-to-date. That means a render object can rely upon [performLayout]
  /// having been called in [hitTest] but cannot rely upon [paint] having been
  /// called. For example, a render object might be a child of a [RenderOpacity]
  /// object, which calls [hitTest] on its children when its opacity is zero
  /// even through it does not [paint] its children.
1849
  bool hitTest(HitTestResult result, { @required Offset position }) {
1850 1851
    assert(() {
      if (!hasSize) {
1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864
        if (debugNeedsLayout) {
          throw new FlutterError(
            'Cannot hit test a render box that has never been laid out.\n'
            'The hitTest() method was called on this RenderBox:\n'
            '  $this\n'
            'Unfortunately, this object\'s geometry is not known at this time, '
            'probably because it has never been laid out. '
            'This means it cannot be accurately hit-tested. If you are trying '
            'to perform a hit test during the layout phase itself, make sure '
            'you only hit test nodes that have completed layout (e.g. the node\'s '
            'children, after their layout() method has been called).'
          );
        }
1865 1866
        throw new FlutterError(
          'Cannot hit test a render box with no size.\n'
1867
          'The hitTest() method was called on this RenderBox:\n'
1868
          '  $this\n'
1869 1870 1871 1872
          'Although this node is not marked as needing layout, '
          'its size is not set. A RenderBox object must have an '
          'explicit size before it can be hit-tested. Make sure '
          'that the RenderBox in question sets its size during layout.'
1873 1874 1875
        );
      }
      return true;
1876
    }());
1877
    if (_size.contains(position)) {
Adam Barth's avatar
Adam Barth committed
1878 1879 1880 1881
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(new BoxHitTestEntry(this, position));
        return true;
      }
1882 1883
    }
    return false;
1884
  }
Adam Barth's avatar
Adam Barth committed
1885

1886
  /// Override this method if this render object can be hit even if its
1887
  /// children were not hit.
1888
  ///
1889 1890 1891 1892 1893
  /// The caller is responsible for transforming [position] from global
  /// coordinates to its location relative to the origin of this [RenderBox].
  /// This [RenderBox] is responsible for checking whether the given position is
  /// within its bounds.
  ///
1894 1895
  /// Used by [hitTest]. If you override [hitTest] and do not call this
  /// function, then you don't need to implement this function.
1896
  @protected
1897
  bool hitTestSelf(Offset position) => false;
Adam Barth's avatar
Adam Barth committed
1898

1899
  /// Override this method to check whether any children are located at the
1900
  /// given position.
Adam Barth's avatar
Adam Barth committed
1901
  ///
1902
  /// Typically children should be hit-tested in reverse paint order so that
Adam Barth's avatar
Adam Barth committed
1903 1904
  /// hit tests at locations where children overlap hit the child that is
  /// visually "on top" (i.e., paints later).
1905
  ///
1906 1907 1908 1909 1910
  /// The caller is responsible for transforming [position] from global
  /// coordinates to its location relative to the origin of this [RenderBox].
  /// This [RenderBox] is responsible for checking whether the given position is
  /// within its bounds.
  ///
1911 1912
  /// Used by [hitTest]. If you override [hitTest] and do not call this
  /// function, then you don't need to implement this function.
1913
  @protected
1914
  bool hitTestChildren(HitTestResult result, { Offset position }) => false;
1915

Adam Barth's avatar
Adam Barth committed
1916
  /// Multiply the transform from the parent's coordinate system to this box's
1917
  /// coordinate system into the given transform.
Adam Barth's avatar
Adam Barth committed
1918 1919 1920 1921
  ///
  /// This function is used to convert coordinate systems between boxes.
  /// Subclasses that apply transforms during painting should override this
  /// function to factor those transforms into the calculation.
1922
  ///
1923
  /// The [RenderBox] implementation takes care of adjusting the matrix for the
1924 1925
  /// position of the given child as determined during layout and stored on the
  /// child's [parentData] in the [BoxParentData.offset] field.
1926
  @override
1927
  void applyPaintTransform(RenderObject child, Matrix4 transform) {
1928
    assert(child != null);
1929
    assert(child.parent == this);
1930 1931 1932 1933 1934
    assert(() {
      if (child.parentData is! BoxParentData) {
        throw new FlutterError(
          '$runtimeType does not implement applyPaintTransform.\n'
          'The following $runtimeType object:\n'
1935
          '  ${toStringShallow()}\n'
1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946
          '...did not use a BoxParentData class for the parentData field of the following child:\n'
          '  ${child.toStringShallow()}\n'
          'The $runtimeType class inherits from RenderBox. '
          'The default applyPaintTransform implementation provided by RenderBox assumes that the '
          'children all use BoxParentData objects for their parentData field. '
          'Since $runtimeType does not in fact use that ParentData class for its children, it must '
          'provide an implementation of applyPaintTransform that supports the specific ParentData '
          'subclass used by its children (which apparently is ${child.parentData.runtimeType}).'
        );
      }
      return true;
1947
    }());
1948 1949
    final BoxParentData childParentData = child.parentData;
    final Offset offset = childParentData.offset;
1950
    transform.translate(offset.dx, offset.dy);
1951 1952
  }

1953
  /// Convert the given point from the global coordinate system in logical pixels
1954
  /// to the local coordinate system for this box.
1955 1956
  ///
  /// If the transform from global coordinates to local coordinates is
1957
  /// degenerate, this function returns [Offset.zero].
1958 1959 1960 1961
  ///
  /// If `ancestor` is non-null, this function converts the given point from the
  /// coordinate system of `ancestor` (which must be an ancestor of this render
  /// object) instead of from the global coordinate system.
1962 1963
  ///
  /// This method is implemented in terms of [getTransformTo].
1964
  Offset globalToLocal(Offset point, { RenderObject ancestor }) {
1965
    final Matrix4 transform = getTransformTo(ancestor);
1966
    final double det = transform.invert();
1967
    if (det == 0.0)
1968
      return Offset.zero;
Hixie's avatar
Hixie committed
1969
    return MatrixUtils.transformPoint(transform, point);
1970 1971
  }

1972
  /// Convert the given point from the local coordinate system for this box to
1973
  /// the global coordinate system in logical pixels.
1974 1975 1976 1977
  ///
  /// If `ancestor` is non-null, this function converts the given point to the
  /// coordinate system of `ancestor` (which must be an ancestor of this render
  /// object) instead of to the global coordinate system.
1978 1979
  ///
  /// This method is implemented in terms of [getTransformTo].
1980
  Offset localToGlobal(Offset point, { RenderObject ancestor }) {
1981
    return MatrixUtils.transformPoint(getTransformTo(ancestor), point);
1982 1983
  }

1984
  /// Returns a rectangle that contains all the pixels painted by this box.
Adam Barth's avatar
Adam Barth committed
1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
  ///
  /// The paint bounds can be larger or smaller than [size], which is the amount
  /// of space this box takes up during layout. For example, if this box casts a
  /// shadow, that shadow might extend beyond the space allocated to this box
  /// during layout.
  ///
  /// The paint bounds are used to size the buffers into which this box paints.
  /// If the box attempts to paints outside its paint bounds, there might not be
  /// enough memory allocated to represent the box's visual appearance, which
  /// can lead to undefined behavior.
  ///
  /// The returned paint bounds are in the local coordinate system of this box.
1997
  @override
1998
  Rect get paintBounds => Offset.zero & size;
Adam Barth's avatar
Adam Barth committed
1999

2000
  /// Override this method to handle pointer events that hit this render object.
2001 2002 2003 2004
  ///
  /// For [RenderBox] objects, the `entry` argument is a [BoxHitTestEntry]. From this
  /// object you can determine the [PointerDownEvent]'s position in local coordinates.
  /// (This is useful because [PointerEvent.position] is in global coordinates.)
2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016
  ///
  /// If you override this, consider calling [debugHandleEvent] as follows, so
  /// that you can support [debugPaintPointersEnabled]:
  ///
  /// ```dart
  /// @override
  /// void handleEvent(PointerEvent event, HitTestEntry entry) {
  ///   assert(debugHandleEvent(event, entry));
  ///   // ... handle the event ...
  /// }
  /// ```
  // TODO(ianh): Fix the type of the argument here once https://github.com/dart-lang/sdk/issues/25232 is fixed
2017
  @override
2018
  void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
2019
    super.handleEvent(event, entry);
2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039
  }

  int _debugActivePointers = 0;

  /// Implements the [debugPaintPointersEnabled] debugging feature.
  ///
  /// [RenderBox] subclasses that implement [handleEvent] should call
  /// [debugHandleEvent] from their [handleEvent] method, as follows:
  ///
  /// ```dart
  /// @override
  /// void handleEvent(PointerEvent event, HitTestEntry entry) {
  ///   assert(debugHandleEvent(event, entry));
  ///   // ... handle the event ...
  /// }
  /// ```
  ///
  /// If you call this for a [PointerDownEvent], make sure you also call it for
  /// the corresponding [PointerUpEvent] or [PointerCancelEvent].
  bool debugHandleEvent(PointerEvent event, HitTestEntry entry) {
2040 2041
    assert(() {
      if (debugPaintPointersEnabled) {
Ian Hickson's avatar
Ian Hickson committed
2042
        if (event is PointerDownEvent) {
2043
          _debugActivePointers += 1;
Ian Hickson's avatar
Ian Hickson committed
2044
        } else if (event is PointerUpEvent || event is PointerCancelEvent) {
2045
          _debugActivePointers -= 1;
Ian Hickson's avatar
Ian Hickson committed
2046
        }
2047 2048 2049
        markNeedsPaint();
      }
      return true;
2050
    }());
2051
    return true;
2052 2053
  }

2054
  @override
2055
  void debugPaint(PaintingContext context, Offset offset) {
2056 2057 2058 2059 2060 2061 2062 2063
    assert(() {
      if (debugPaintSizeEnabled)
        debugPaintSize(context, offset);
      if (debugPaintBaselinesEnabled)
        debugPaintBaselines(context, offset);
      if (debugPaintPointersEnabled)
        debugPaintPointers(context, offset);
      return true;
2064
    }());
2065
  }
2066 2067 2068 2069

  /// In debug mode, paints a border around this render box.
  ///
  /// Called for every [RenderBox] when [debugPaintSizeEnabled] is true.
2070
  @protected
2071
  void debugPaintSize(PaintingContext context, Offset offset) {
2072
    assert(() {
2073
      final Paint paint = new Paint()
2074
       ..style = PaintingStyle.stroke
2075
       ..strokeWidth = 1.0
2076
       ..color = const Color(0xFF00FFFF);
2077 2078
      context.canvas.drawRect((offset & size).deflate(0.5), paint);
      return true;
2079
    }());
2080
  }
2081 2082 2083 2084

  /// In debug mode, paints a line for each baseline.
  ///
  /// Called for every [RenderBox] when [debugPaintBaselinesEnabled] is true.
2085
  @protected
2086
  void debugPaintBaselines(PaintingContext context, Offset offset) {
2087
    assert(() {
2088
      final Paint paint = new Paint()
2089
       ..style = PaintingStyle.stroke
2090 2091 2092
       ..strokeWidth = 0.25;
      Path path;
      // ideographic baseline
2093
      final double baselineI = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true);
2094
      if (baselineI != null) {
2095
        paint.color = const Color(0xFFFFD000);
2096 2097 2098 2099 2100 2101
        path = new Path();
        path.moveTo(offset.dx, offset.dy + baselineI);
        path.lineTo(offset.dx + size.width, offset.dy + baselineI);
        context.canvas.drawPath(path, paint);
      }
      // alphabetic baseline
2102
      final double baselineA = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true);
2103
      if (baselineA != null) {
2104
        paint.color = const Color(0xFF00FF00);
2105 2106 2107 2108 2109 2110
        path = new Path();
        path.moveTo(offset.dx, offset.dy + baselineA);
        path.lineTo(offset.dx + size.width, offset.dy + baselineA);
        context.canvas.drawPath(path, paint);
      }
      return true;
2111
    }());
2112
  }
2113

2114 2115
  /// In debug mode, paints a rectangle if this render box has counted more
  /// pointer downs than pointer up events.
2116 2117
  ///
  /// Called for every [RenderBox] when [debugPaintPointersEnabled] is true.
2118 2119 2120
  ///
  /// By default, events are not counted. For details on how to ensure that
  /// events are counted for your class, see [debugHandleEvent].
2121
  @protected
2122
  void debugPaintPointers(PaintingContext context, Offset offset) {
2123 2124
    assert(() {
      if (_debugActivePointers > 0) {
2125
        final Paint paint = new Paint()
2126
         ..color = new Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000));
2127 2128 2129
        context.canvas.drawRect(offset & size, paint);
      }
      return true;
2130
    }());
2131
  }
2132

2133
  @override
2134 2135 2136
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Size>('size', _size, missingIfNull: true));
2137
  }
2138 2139
}

2140 2141
/// A mixin that provides useful default behaviors for boxes with children
/// managed by the [ContainerRenderObjectMixin] mixin.
Adam Barth's avatar
Adam Barth committed
2142 2143 2144 2145
///
/// By convention, this class doesn't override any members of the superclass.
/// Instead, it provides helpful functions that subclasses can call as
/// appropriate.
2146 2147 2148 2149
abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerBoxParentData<ChildType>> implements ContainerRenderObjectMixin<ChildType, ParentDataType> {
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory RenderBoxContainerDefaultsMixin._() => null;
2150

2151
  /// Returns the baseline of the first child with a baseline.
Adam Barth's avatar
Adam Barth committed
2152 2153 2154
  ///
  /// Useful when the children are displayed vertically in the same order they
  /// appear in the child list.
2155
  double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) {
2156
    assert(!debugNeedsLayout);
Adam Barth's avatar
Adam Barth committed
2157
    ChildType child = firstChild;
2158
    while (child != null) {
Hixie's avatar
Hixie committed
2159
      final ParentDataType childParentData = child.parentData;
2160
      final double result = child.getDistanceToActualBaseline(baseline);
2161
      if (result != null)
2162
        return result + childParentData.offset.dy;
Hixie's avatar
Hixie committed
2163
      child = childParentData.nextSibling;
2164 2165 2166 2167
    }
    return null;
  }

2168
  /// Returns the minimum baseline value among every child.
Adam Barth's avatar
Adam Barth committed
2169 2170 2171
  ///
  /// Useful when the vertical position of the children isn't determined by the
  /// order in the child list.
2172
  double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
2173
    assert(!debugNeedsLayout);
2174
    double result;
Adam Barth's avatar
Adam Barth committed
2175
    ChildType child = firstChild;
2176
    while (child != null) {
Hixie's avatar
Hixie committed
2177
      final ParentDataType childParentData = child.parentData;
2178 2179
      double candidate = child.getDistanceToActualBaseline(baseline);
      if (candidate != null) {
2180
        candidate += childParentData.offset.dy;
2181 2182 2183 2184 2185
        if (result != null)
          result = math.min(result, candidate);
        else
          result = candidate;
      }
Hixie's avatar
Hixie committed
2186
      child = childParentData.nextSibling;
2187 2188 2189 2190
    }
    return result;
  }

2191
  /// Performs a hit test on each child by walking the child list backwards.
Adam Barth's avatar
Adam Barth committed
2192 2193 2194
  ///
  /// Stops walking once after the first child reports that it contains the
  /// given point. Returns whether any children contain the given point.
2195 2196 2197 2198 2199
  ///
  /// See also:
  ///
  ///  * [defaultPaint], which paints the children appropriate for this
  ///    hit-testing strategy.
2200
  bool defaultHitTestChildren(HitTestResult result, { Offset position }) {
2201 2202 2203
    // the x, y parameters have the top left of the node's box as the origin
    ChildType child = lastChild;
    while (child != null) {
Hixie's avatar
Hixie committed
2204
      final ParentDataType childParentData = child.parentData;
2205
      if (child.hitTest(result, position: position - childParentData.offset))
2206
        return true;
Hixie's avatar
Hixie committed
2207
      child = childParentData.previousSibling;
2208
    }
2209
    return false;
2210 2211
  }

2212
  /// Paints each child by walking the child list forwards.
2213 2214 2215 2216 2217
  ///
  /// See also:
  ///
  ///  * [defaultHitTestChildren], which implements hit-testing of the children
  ///    in a manner appropriate for this painting strategy.
2218
  void defaultPaint(PaintingContext context, Offset offset) {
Adam Barth's avatar
Adam Barth committed
2219
    ChildType child = firstChild;
2220
    while (child != null) {
Hixie's avatar
Hixie committed
2221
      final ParentDataType childParentData = child.parentData;
Adam Barth's avatar
Adam Barth committed
2222
      context.paintChild(child, childParentData.offset + offset);
Hixie's avatar
Hixie committed
2223
      child = childParentData.nextSibling;
2224 2225
    }
  }
Adam Barth's avatar
Adam Barth committed
2226

2227 2228 2229 2230 2231
  /// Returns a list containing the children of this render object.
  ///
  /// This function is useful when you need random-access to the children of
  /// this render object. If you're accessing the children in order, consider
  /// walking the child list directly.
Adam Barth's avatar
Adam Barth committed
2232
  List<ChildType> getChildrenAsList() {
2233
    final List<ChildType> result = <ChildType>[];
Adam Barth's avatar
Adam Barth committed
2234 2235 2236 2237 2238 2239 2240 2241
    RenderBox child = firstChild;
    while (child != null) {
      final ParentDataType childParentData = child.parentData;
      result.add(child);
      child = childParentData.nextSibling;
    }
    return result;
  }
2242
}