box.dart 107 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
///
/// ## 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].
///
78 79 80 81
/// An axis whose _minimum_ constraint is infinite is just said to be _infinite_
/// (since by definition the maximum constraint must also be infinite in that
/// case). See: [hasInfiniteWidth], [hasInfiniteHeight].
///
82 83 84 85
/// A size is _constrained_ when it satisfies a [BoxConstraints] description.
/// See: [constrain], [constrainWidth], [constrainHeight],
/// [constrainDimensions], [constrainSizeAndAttemptToPreserveAspectRatio],
/// [isSatisfiedBy].
86
class BoxConstraints extends Constraints {
87
  /// Creates box constraints with the given constraints.
88
  const BoxConstraints({
89 90 91
    this.minWidth = 0.0,
    this.maxWidth = double.infinity,
    this.minHeight = 0.0,
92
    this.maxHeight = double.infinity,
93 94
  });

95
  /// Creates box constraints that is respected only by the given size.
96 97 98 99 100 101
  BoxConstraints.tight(Size size)
    : minWidth = size.width,
      maxWidth = size.width,
      minHeight = size.height,
      maxHeight = size.height;

102
  /// Creates box constraints that require the given width or height.
103 104 105 106 107 108
  ///
  /// 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.
109 110
  const BoxConstraints.tightFor({
    double width,
111
    double height,
112 113 114 115
  }) : minWidth = width ?? 0.0,
       maxWidth = width ?? double.infinity,
       minHeight = height ?? 0.0,
       maxHeight = height ?? double.infinity;
116

117 118 119 120 121 122 123
  /// 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.
124
  const BoxConstraints.tightForFinite({
125
    double width = double.infinity,
126
    double height = double.infinity,
127 128 129 130
  }) : 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;
131

132
  /// Creates box constraints that forbid sizes larger than the given size.
133 134 135 136 137 138
  BoxConstraints.loose(Size size)
    : minWidth = 0.0,
      maxWidth = size.width,
      minHeight = 0.0,
      maxHeight = size.height;

139
  /// Creates box constraints that expand to fill another box constraints.
140 141 142 143 144
  ///
  /// If width or height is given, the constraints will require exactly the
  /// given value in the given dimension.
  const BoxConstraints.expand({
    double width,
145
    double height,
146 147 148 149
  }) : minWidth = width ?? double.infinity,
       maxWidth = width ?? double.infinity,
       minHeight = height ?? double.infinity,
       maxHeight = height ?? double.infinity;
150

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  /// The minimum width that satisfies the constraints.
  final double minWidth;

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

  /// The minimum height that satisfies the constraints.
  final double minHeight;

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

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

182
  /// Returns new box constraints that are smaller by the given edge dimensions.
183
  BoxConstraints deflate(EdgeInsets edges) {
184
    assert(edges != null);
185
    assert(debugAssertIsValid());
186 187 188 189
    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);
190
    return BoxConstraints(
191 192 193
      minWidth: deflatedMinWidth,
      maxWidth: math.max(deflatedMinWidth, maxWidth - horizontal),
      minHeight: deflatedMinHeight,
194
      maxHeight: math.max(deflatedMinHeight, maxHeight - vertical),
195 196 197
    );
  }

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

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

220 221 222 223
  /// 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 }) {
224
    return BoxConstraints(minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth),
225 226 227
                              maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth),
                              minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight),
                              maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight));
228 229
  }

230 231
  /// A box constraints with the width and height constraints flipped.
  BoxConstraints get flipped {
232
    return BoxConstraints(
233 234 235
      minWidth: minHeight,
      maxWidth: maxHeight,
      minHeight: minWidth,
236
      maxHeight: maxWidth,
237 238 239
    );
  }

240
  /// Returns box constraints with the same width constraints but with
241
  /// unconstrained height.
242
  BoxConstraints widthConstraints() => BoxConstraints(minWidth: minWidth, maxWidth: maxWidth);
243

244
  /// Returns box constraints with the same height constraints but with
245
  /// unconstrained width
246
  BoxConstraints heightConstraints() => BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);
247

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

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

262
  Size _debugPropagateDebugSize(Size size, Size result) {
263 264
    assert(() {
      if (size is _DebugSize)
265
        result = _DebugSize(result, size._owner, size._canBeUsedByParent);
266
      return true;
267
    }());
268 269
    return result;
  }
270

271 272 273 274 275 276
  /// 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) {
277
    Size result = Size(constrainWidth(size.width), constrainHeight(size.height));
278
    assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
279 280 281 282 283 284 285 286 287
    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) {
288
    return Size(constrainWidth(width), constrainHeight(height));
289 290
  }

Adam Barth's avatar
Adam Barth committed
291 292
  /// Returns a size that attempts to meet the following conditions, in order:
  ///
293 294
  ///  * 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
295
  ///    given size.
296
  ///  * The returned size as big as possible while still being equal to or
Adam Barth's avatar
Adam Barth committed
297 298
  ///    smaller than the given size.
  Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) {
299 300
    if (isTight) {
      Size result = smallest;
301
      assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
302 303
      return result;
    }
Adam Barth's avatar
Adam Barth committed
304 305 306 307 308

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

    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;
    }

331
    Size result = Size(constrainWidth(width), constrainHeight(height));
332
    assert(() { result = _debugPropagateDebugSize(size, result); return true; }());
333
    return result;
Adam Barth's avatar
Adam Barth committed
334 335
  }

336
  /// The biggest size that satisfies the constraints.
337
  Size get biggest => Size(constrainWidth(), constrainHeight());
338

339
  /// The smallest size that satisfies the constraints.
340
  Size get smallest => Size(constrainWidth(0.0), constrainHeight(0.0));
341

342
  /// Whether there is exactly one width value that satisfies the constraints.
343
  bool get hasTightWidth => minWidth >= maxWidth;
344

345
  /// Whether there is exactly one height value that satisfies the constraints.
346
  bool get hasTightHeight => minHeight >= maxHeight;
347

348
  /// Whether there is exactly one size that satisfies the constraints.
349
  @override
350 351
  bool get isTight => hasTightWidth && hasTightHeight;

352
  /// Whether there is an upper bound on the maximum width.
353 354 355 356 357 358
  ///
  /// See also:
  ///
  ///  * [hasBoundedHeight], the equivalent for the vertical axis.
  ///  * [hasInfiniteWidth], which describes whether the minimum width
  ///    constraint is infinite.
359
  bool get hasBoundedWidth => maxWidth < double.infinity;
360 361

  /// Whether there is an upper bound on the maximum height.
362 363 364 365 366 367
  ///
  /// See also:
  ///
  ///  * [hasBoundedWidth], the equivalent for the horizontal axis.
  ///  * [hasInfiniteHeight], which describes whether the minimum height
  ///    constraint is infinite.
368
  bool get hasBoundedHeight => maxHeight < double.infinity;
369

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
  /// Whether the width constraint is infinite.
  ///
  /// Such a constraint is used to indicate that a box should grow as large as
  /// some other constraint (in this case, horizontally). If constraints are
  /// infinite, then they must have other (non-infinite) constraints [enforce]d
  /// upon them, or must be [tighten]ed, before they can be used to derive a
  /// [Size] for a [RenderBox.size].
  ///
  /// See also:
  ///
  ///  * [hasInfiniteHeight], the equivalent for the vertical axis.
  ///  * [hasBoundedWidth], which describes whether the maximum width
  ///    constraint is finite.
  bool get hasInfiniteWidth => minWidth >= double.infinity;

  /// Whether the height constraint is infinite.
  ///
  /// Such a constraint is used to indicate that a box should grow as large as
  /// some other constraint (in this case, vertically). If constraints are
  /// infinite, then they must have other (non-infinite) constraints [enforce]d
  /// upon them, or must be [tighten]ed, before they can be used to derive a
  /// [Size] for a [RenderBox.size].
  ///
  /// See also:
  ///
  ///  * [hasInfiniteWidth], the equivalent for the horizontal axis.
  ///  * [hasBoundedHeight], which describes whether the maximum height
  ///    constraint is finite.
  bool get hasInfiniteHeight => minHeight >= double.infinity;

400
  /// Whether the given size satisfies the constraints.
401
  bool isSatisfiedBy(Size size) {
402
    assert(debugAssertIsValid());
403 404
    return (minWidth <= size.width) && (size.width <= maxWidth) &&
           (minHeight <= size.height) && (size.height <= maxHeight);
405 406
  }

407 408
  /// Scales each constraint parameter by the given factor.
  BoxConstraints operator*(double factor) {
409
    return BoxConstraints(
410 411 412
      minWidth: minWidth * factor,
      maxWidth: maxWidth * factor,
      minHeight: minHeight * factor,
413
      maxHeight: maxHeight * factor,
Adam Barth's avatar
Adam Barth committed
414 415 416
    );
  }

417 418
  /// Scales each constraint parameter by the inverse of the given factor.
  BoxConstraints operator/(double factor) {
419
    return BoxConstraints(
420 421 422
      minWidth: minWidth / factor,
      maxWidth: maxWidth / factor,
      minHeight: minHeight / factor,
423
      maxHeight: maxHeight / factor,
Adam Barth's avatar
Adam Barth committed
424 425 426
    );
  }

427 428
  /// Scales each constraint parameter by the inverse of the given factor, rounded to the nearest integer.
  BoxConstraints operator~/(double factor) {
429
    return BoxConstraints(
430 431 432
      minWidth: (minWidth ~/ factor).toDouble(),
      maxWidth: (maxWidth ~/ factor).toDouble(),
      minHeight: (minHeight ~/ factor).toDouble(),
433
      maxHeight: (maxHeight ~/ factor).toDouble(),
Adam Barth's avatar
Adam Barth committed
434 435 436
    );
  }

437 438
  /// Computes the remainder of each constraint parameter by the given value.
  BoxConstraints operator%(double value) {
439
    return BoxConstraints(
440 441 442
      minWidth: minWidth % value,
      maxWidth: maxWidth % value,
      minHeight: minHeight % value,
443
      maxHeight: maxHeight % value,
Adam Barth's avatar
Adam Barth committed
444 445 446
    );
  }

447
  /// Linearly interpolate between two BoxConstraints.
Adam Barth's avatar
Adam Barth committed
448
  ///
449 450
  /// If either is null, this function interpolates from a [BoxConstraints]
  /// object whose fields are all set to 0.0.
451
  ///
452
  /// {@macro dart.ui.shadow.lerp}
Adam Barth's avatar
Adam Barth committed
453
  static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, double t) {
454
    assert(t != null);
Adam Barth's avatar
Adam Barth committed
455 456 457 458 459 460
    if (a == null && b == null)
      return null;
    if (a == null)
      return b * t;
    if (b == null)
      return a * (1.0 - t);
461 462
    assert(a.debugAssertIsValid());
    assert(b.debugAssertIsValid());
463 464 465 466
    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.');
467
    return BoxConstraints(
468 469 470 471
      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
472 473 474
    );
  }

475
  /// Returns whether the object's constraints are normalized.
476
  /// Constraints are normalized if the minimums are less than or
477 478 479 480 481 482 483 484 485
  /// 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.
486
  @override
487 488 489 490 491 492
  bool get isNormalized {
    return minWidth >= 0.0 &&
           minWidth <= maxWidth &&
           minHeight >= 0.0 &&
           minHeight <= maxHeight;
  }
493

494
  @override
495
  bool debugAssertIsValid({
496
    bool isAppliedConstraint = false,
497
    InformationCollector informationCollector,
498
  }) {
499
    assert(() {
500
      void throwError(DiagnosticsNode message) {
501 502 503 504 505
        throw FlutterError.fromParts(<DiagnosticsNode>[
          message,
          if (informationCollector != null) ...informationCollector(),
          DiagnosticsProperty<BoxConstraints>('The offending constraints were', this, style: DiagnosticsTreeStyle.errorProperty),
        ]);
506
      }
507
      if (minWidth.isNaN || maxWidth.isNaN || minHeight.isNaN || maxHeight.isNaN) {
508
        final List<String> affectedFieldsList = <String>[];
509 510 511 512 513 514 515 516
        if (minWidth.isNaN)
          affectedFieldsList.add('minWidth');
        if (maxWidth.isNaN)
          affectedFieldsList.add('maxWidth');
        if (minHeight.isNaN)
          affectedFieldsList.add('minHeight');
        if (maxHeight.isNaN)
          affectedFieldsList.add('maxHeight');
517
        assert(affectedFieldsList.isNotEmpty);
518 519 520 521 522 523 524 525 526 527
        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;
        }
528
        throwError(ErrorSummary('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.'));
529
      }
530
      if (minWidth < 0.0 && minHeight < 0.0)
531
        throwError(ErrorSummary('BoxConstraints has both a negative minimum width and a negative minimum height.'));
532
      if (minWidth < 0.0)
533
        throwError(ErrorSummary('BoxConstraints has a negative minimum width.'));
534
      if (minHeight < 0.0)
535
        throwError(ErrorSummary('BoxConstraints has a negative minimum height.'));
536
      if (maxWidth < minWidth && maxHeight < minHeight)
537
        throwError(ErrorSummary('BoxConstraints has both width and height constraints non-normalized.'));
538
      if (maxWidth < minWidth)
539
        throwError(ErrorSummary('BoxConstraints has non-normalized width constraints.'));
540
      if (maxHeight < minHeight)
541
        throwError(ErrorSummary('BoxConstraints has non-normalized height constraints.'));
542 543
      if (isAppliedConstraint) {
        if (minWidth.isInfinite && minHeight.isInfinite)
544
          throwError(ErrorSummary('BoxConstraints forces an infinite width and infinite height.'));
545
        if (minWidth.isInfinite)
546
          throwError(ErrorSummary('BoxConstraints forces an infinite width.'));
547
        if (minHeight.isInfinite)
548
          throwError(ErrorSummary('BoxConstraints forces an infinite height.'));
549 550 551
      }
      assert(isNormalized);
      return true;
552
    }());
553 554 555
    return isNormalized;
  }

556 557 558 559
  /// 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].
560
  BoxConstraints normalize() {
561 562 563 564
    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;
565
    return BoxConstraints(
566 567 568
      minWidth: minWidth,
      maxWidth: minWidth > maxWidth ? minWidth : maxWidth,
      minHeight: minHeight,
569
      maxHeight: minHeight > maxHeight ? minHeight : maxHeight,
570 571 572
    );
  }

573
  @override
Hixie's avatar
Hixie committed
574
  bool operator ==(dynamic other) {
575
    assert(debugAssertIsValid());
576 577
    if (identical(this, other))
      return true;
578
    if (runtimeType != other.runtimeType)
Hixie's avatar
Hixie committed
579 580
      return false;
    final BoxConstraints typedOther = other;
581
    assert(typedOther.debugAssertIsValid());
Hixie's avatar
Hixie committed
582 583 584 585
    return minWidth == typedOther.minWidth &&
           maxWidth == typedOther.maxWidth &&
           minHeight == typedOther.minHeight &&
           maxHeight == typedOther.maxHeight;
586
  }
Hixie's avatar
Hixie committed
587

588
  @override
589
  int get hashCode {
590
    assert(debugAssertIsValid());
591
    return hashValues(minWidth, maxWidth, minHeight, maxHeight);
592 593
  }

594
  @override
Hixie's avatar
Hixie committed
595
  String toString() {
596
    final String annotation = isNormalized ? '' : '; NOT NORMALIZED';
597
    if (minWidth == double.infinity && minHeight == double.infinity)
598
      return 'BoxConstraints(biggest$annotation)';
599 600
    if (minWidth == 0 && maxWidth == double.infinity &&
        minHeight == 0 && maxHeight == double.infinity)
601
      return 'BoxConstraints(unconstrained$annotation)';
Hixie's avatar
Hixie committed
602 603 604 605 606 607 608
    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');
609
    return 'BoxConstraints($width, $height$annotation)';
Hixie's avatar
Hixie committed
610
  }
611 612
}

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
/// Method signature for hit testing a [RenderBox].
///
/// Used by [BoxHitTestResult.addWithPaintTransform] to hit test children
/// of a [RenderBox].
///
/// See also:
///
///  * [RenderBox.hitTest], which documents more details around hit testing
///    [RenderBox]es.
typedef BoxHitTest = bool Function(BoxHitTestResult result, Offset position);

/// The result of performing a hit test on [RenderBox]es.
///
/// An instance of this class is provided to [RenderBox.hitTest] to record the
/// result of the hit test.
class BoxHitTestResult extends HitTestResult {
  /// Creates an empty hit test result for hit testing on [RenderBox].
  BoxHitTestResult() : super();

  /// Wraps `result` to create a [HitTestResult] that implements the
  /// [BoxHitTestResult] protocol for hit testing on [RenderBox]es.
  ///
  /// This method is used by [RenderObject]s that adapt between the
  /// [RenderBox]-world and the non-[RenderBox]-world to convert a (subtype of)
  /// [HitTestResult] to a [BoxHitTestResult] for hit testing on [RenderBox]es.
  ///
  /// The [HitTestEntry] instances added to the returned [BoxHitTestResult] are
  /// also added to the wrapped `result` (both share the same underlying data
  /// structure to store [HitTestEntry] instances).
  ///
  /// See also:
  ///
  ///  * [HitTestResult.wrap], which turns a [BoxHitTestResult] back into a
  ///    generic [HitTestResult].
  ///  * [SliverHitTestResult.wrap], which turns a [BoxHitTestResult] into a
  ///    [SliverHitTestResult] for hit testing on [RenderSliver] children.
  BoxHitTestResult.wrap(HitTestResult result) : super.wrap(result);

  /// Transforms `position` to the local coordinate system of a child for
  /// hit-testing the child.
  ///
  /// The actual hit testing of the child needs to be implemented in the
  /// provided `hitTest` callback, which is invoked with the transformed
  /// `position` as argument.
  ///
658 659 660 661
  /// The provided paint `transform` (which describes the transform from the
  /// child to the parent in 3D) is processed by
  /// [PointerEvent.removePerspectiveTransform] to remove the
  /// perspective component and inverted before it is used to transform
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
  /// `position` from the coordinate system of the parent to the system of the
  /// child.
  ///
  /// If `transform` is null it will be treated as the identity transform and
  /// `position` is provided to the `hitTest` callback as-is. If `transform`
  /// cannot be inverted, the `hitTest` callback is not invoked and false is
  /// returned. Otherwise, the return value of the `hitTest` callback is
  /// returned.
  ///
  /// The `position` argument may be null, which will be forwarded to the
  /// `hitTest` callback as-is. Using null as the position can be useful if
  /// the child speaks a different hit test protocol then the parent and the
  /// position is not required to do the actual hit testing in that protocol.
  ///
  /// {@tool sample}
677 678
  /// This method is used in [RenderBox.hitTestChildren] when the child and
  /// parent don't share the same origin.
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
  ///
  /// ```dart
  /// abstract class Foo extends RenderBox {
  ///
  ///   final Matrix4 _effectiveTransform = Matrix4.rotationZ(50);
  ///
  ///   @override
  ///   void applyPaintTransform(RenderBox child, Matrix4 transform) {
  ///     transform.multiply(_effectiveTransform);
  ///   }
  ///
  ///   @override
  ///   bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
  ///     return result.addWithPaintTransform(
  ///       transform: _effectiveTransform,
  ///       position: position,
  ///       hitTest: (BoxHitTestResult result, Offset position) {
  ///         return super.hitTestChildren(result, position: position);
  ///       },
  ///     );
  ///   }
  /// }
  /// ```
  /// {@end-tool}
  ///
  /// See also:
  ///
  ///  * [addWithPaintOffset], which can be used for `transform`s that are just
  ///    simple matrix translations by an [Offset].
  ///  * [addWithRawTransform], which takes a transform matrix that is directly
  ///    used to transform the position without any pre-processing.
  bool addWithPaintTransform({
    @required Matrix4 transform,
    @required Offset position,
    @required BoxHitTest hitTest,
  }) {
    assert(hitTest != null);
    if (transform != null) {
717
      transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
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 751 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
      if (transform == null) {
        // Objects are not visible on screen and cannot be hit-tested.
        return false;
      }
    }
    return addWithRawTransform(
      transform: transform,
      position: position,
      hitTest: hitTest,
    );
  }

  /// Convenience method for hit testing children, that are translated by
  /// an [Offset].
  ///
  /// The actual hit testing of the child needs to be implemented in the
  /// provided `hitTest` callback, which is invoked with the transformed
  /// `position` as argument.
  ///
  /// This method can be used as a convenience over [addWithPaintTransform] if
  /// a parent paints a child at an `offset`.
  ///
  /// A null value for `offset` is treated as if [Offset.zero] was provided.
  ///
  /// Se also:
  ///
  ///  * [addWithPaintTransform], which takes a generic paint transform matrix and
  ///    documents the intended usage of this API in more detail.
  bool addWithPaintOffset({
    @required Offset offset,
    @required Offset position,
    @required BoxHitTest hitTest,
  }) {
    assert(hitTest != null);
    return addWithRawTransform(
      transform: offset != null ? Matrix4.translationValues(-offset.dx, -offset.dy, 0.0) : null,
      position: position,
      hitTest: hitTest,
    );
  }

  /// Transforms `position` to the local coordinate system of a child for
  /// hit-testing the child.
  ///
  /// The actual hit testing of the child needs to be implemented in the
  /// provided `hitTest` callback, which is invoked with the transformed
  /// `position` as argument.
  ///
  /// Unlike [addWithPaintTransform], the provided `transform` matrix is used
  /// directly to transform `position` without any pre-processing.
  ///
  /// If `transform` is null it will be treated as the identity transform ad
  /// `position` is provided to the `hitTest` callback as-is.
  ///
  /// The function returns the return value of the `hitTest` callback.
  ///
  /// The `position` argument may be null, which will be forwarded to the
  /// `hitTest` callback as-is. Using null as the position can be useful if
  /// the child speaks a different hit test protocol then the parent and the
  /// position is not required to do the actual hit testing in that protocol.
  ///
  /// Se also:
  ///
  ///  * [addWithPaintTransform], which accomplishes the same thing, but takes a
  ///    _paint_ transform matrix.
  bool addWithRawTransform({
    @required Matrix4 transform,
    @required Offset position,
    @required BoxHitTest hitTest,
  }) {
    assert(hitTest != null);
    final Offset transformedPosition = position == null || transform == null
        ? position
        : MatrixUtils.transformPoint(transform, position);
792 793 794 795 796 797 798 799
    if (transform != null) {
      pushTransform(transform);
    }
    final bool isHit = hitTest(this, transformedPosition);
    if (transform != null) {
      popTransform();
    }
    return isHit;
800 801 802
  }
}

803
/// A hit test entry used by [RenderBox].
804
class BoxHitTestEntry extends HitTestEntry {
805 806 807
  /// Creates a box hit test entry.
  ///
  /// The [localPosition] argument must not be null.
808
  BoxHitTestEntry(RenderBox target, this.localPosition)
809 810
    : assert(localPosition != null),
      super(target);
Ian Hickson's avatar
Ian Hickson committed
811

812
  @override
Ian Hickson's avatar
Ian Hickson committed
813
  RenderBox get target => super.target;
Adam Barth's avatar
Adam Barth committed
814

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

818
  @override
819
  String toString() => '${describeIdentity(target)}@$localPosition';
820 821
}

822
/// Parent data used by [RenderBox] and its subclasses.
823
class BoxParentData extends ParentData {
824
  /// The offset at which to paint the child in the parent's coordinate system.
825
  Offset offset = Offset.zero;
826

827
  @override
828
  String toString() => 'offset=$offset';
829 830
}

Hixie's avatar
Hixie committed
831 832
/// Abstract ParentData subclass for RenderBox subclasses that want the
/// ContainerRenderObjectMixin.
833 834 835 836
///
/// 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
837

838
enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight }
839 840

@immutable
841
class _IntrinsicDimensionsCacheEntry {
842
  const _IntrinsicDimensionsCacheEntry(this.dimension, this.argument);
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859

  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);
}

860
/// A render object in a 2D Cartesian coordinate system.
Adam Barth's avatar
Adam Barth committed
861
///
862 863 864 865 866
/// 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
867 868
///
/// Box layout is performed by passing a [BoxConstraints] object down the tree.
869 870
/// 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
871 872 873
/// given to it by its parent.
///
/// This protocol is sufficient for expressing a number of common box layout
874
/// data flows. For example, to implement a width-in-height-out data flow, call
Adam Barth's avatar
Adam Barth committed
875 876 877
/// 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.
878 879 880 881 882
///
/// ## 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
883
/// remaining in the Cartesian space defined by the [RenderBox] protocol.
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 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
///
/// 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
978
/// initialize the [parentData] field of a child when the child is attached.
979 980 981 982 983 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 1016 1017 1018 1019
///
/// 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
1020
/// can extend [ContainerBoxParentData]; this is essentially
1021 1022 1023 1024 1025
/// [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
1026
/// class FooParentData extends ContainerBoxParentData<RenderBox> {
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
///   // (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
1039
/// class RenderFoo extends RenderBox with
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
///   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).
///
1083
/// * Implement [debugDescribeChildren] such that it outputs a [DiagnosticsNode]
1084
///   for each child.
1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
///
/// 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
1102
/// significant optimizations. Classes that use this approach should override
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127
/// [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
1128
/// apply some optimizations, as it knows that if the constraints are tight, the
1129 1130 1131 1132 1133 1134 1135 1136 1137
/// 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.
///
1138
/// This flag turns off some optimizations; algorithms that do not rely on the
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
/// 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
1156
/// to read that output, and optimizations still kick in if the child has tight
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
/// 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].
///
1210
/// The [hitTest] method itself is given an [Offset], and must return true if the
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242
/// 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
1243
/// the [semanticsAnnotator] getter. The default implementations are sufficient
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
/// 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.
1267
abstract class RenderBox extends RenderObject {
1268
  @override
1269
  void setupParentData(covariant RenderObject child) {
1270
    if (child.parentData is! BoxParentData)
1271
      child.parentData = BoxParentData();
1272 1273
  }

1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
  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;
1285
    }());
1286 1287 1288
    if (shouldCache) {
      _cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
      return _cachedIntrinsicDimensions.putIfAbsent(
1289
        _IntrinsicDimensionsCacheEntry(dimension, argument),
1290
        () => computer(argument),
1291 1292 1293 1294 1295
      );
    }
    return computer(argument);
  }

1296 1297 1298 1299 1300 1301 1302
  /// 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
1303
  ///
1304 1305 1306 1307 1308 1309 1310
  /// 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.
  ///
1311
  /// Do not override this method. Instead, implement [computeMinIntrinsicWidth].
1312 1313
  @mustCallSuper
  double getMinIntrinsicWidth(double height) {
1314 1315
    assert(() {
      if (height == null) {
1316 1317 1318 1319 1320
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The height argument to getMinIntrinsicWidth was null.'),
          ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'),
          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.')
        ]);
1321 1322
      }
      if (height < 0.0) {
1323 1324 1325 1326 1327 1328 1329 1330 1331
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The height argument to getMinIntrinsicWidth was negative.'),
          ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'),
          ErrorHint(
            '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.'
          ),
        ]);
1332 1333
      }
      return true;
1334
    }());
1335 1336 1337 1338 1339 1340 1341 1342 1343
    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.
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
  ///
  /// 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.
  ///
1355 1356
  /// The `height` argument will never be negative or null. It may be infinite.
  ///
1357 1358 1359 1360
  /// 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`.
  ///
1361 1362 1363 1364 1365 1366 1367 1368 1369 1370
  /// 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
1371
  /// returned from [computeMaxIntrinsicWidth]) would be the width of the string
1372 1373 1374 1375 1376 1377
  /// 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,
1378
  /// this wouldn't be a _correct_ rendering, and [computeMinIntrinsicWidth] is
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
  /// 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.
  ///
1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415
  /// ### 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]).
1416 1417 1418 1419 1420 1421 1422 1423
  ///
  /// ### 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
1424 1425 1426
  /// [computeMinIntrinsicWidth] and [computeMinIntrinsicHeight] are in terms of
  /// what the dimensions _could be_, and such boxes can only be one size in
  /// such cases.
1427 1428 1429
  ///
  /// When the incoming argument is not finite, then they should return the
  /// actual intrinsic dimensions based on the contents, as any other box would.
1430 1431
  @protected
  double computeMinIntrinsicWidth(double height) {
1432
    return 0.0;
1433 1434
  }

Adam Barth's avatar
Adam Barth committed
1435
  /// Returns the smallest width beyond which increasing the width never
1436 1437
  /// 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
1438
  ///
1439 1440 1441 1442 1443
  /// 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.
  ///
1444 1445 1446 1447 1448 1449 1450
  /// 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.
  ///
1451
  /// Do not override this method. Instead, implement
1452 1453 1454
  /// [computeMaxIntrinsicWidth].
  @mustCallSuper
  double getMaxIntrinsicWidth(double height) {
1455 1456
    assert(() {
      if (height == null) {
1457 1458 1459 1460 1461
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The height argument to getMaxIntrinsicWidth was null.'),
          ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'),
          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.')
        ]);
1462 1463
      }
      if (height < 0.0) {
1464 1465 1466 1467 1468 1469 1470 1471 1472
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The height argument to getMaxIntrinsicWidth was negative.'),
          ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'),
          ErrorHint(
            '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.'
          )
        ]);
1473 1474
      }
      return true;
1475
    }());
1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
    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.
1486 1487 1488
  ///
  /// If the layout algorithm is strictly height-in-width-out, or is
  /// height-in-width-out when the width is unconstrained, then this should
1489
  /// return the same value as [computeMinIntrinsicWidth] for the same height.
1490 1491 1492
  ///
  /// Otherwise, the height argument should be ignored, and the returned value
  /// should be equal to or bigger than the value returned by
1493
  /// [computeMinIntrinsicWidth].
1494
  ///
1495 1496
  /// The `height` argument will never be negative or null. It may be infinite.
  ///
1497 1498 1499 1500 1501
  /// 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.
  ///
1502 1503 1504 1505
  /// 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`.
  ///
1506 1507
  /// This function should never return a negative or infinite value.
  ///
1508 1509 1510
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
  double computeMaxIntrinsicWidth(double height) {
1511
    return 0.0;
1512 1513
  }

1514 1515 1516 1517 1518 1519 1520
  /// 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
1521
  ///
1522 1523 1524 1525 1526 1527 1528
  /// 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.
  ///
1529
  /// Do not override this method. Instead, implement
1530 1531 1532
  /// [computeMinIntrinsicHeight].
  @mustCallSuper
  double getMinIntrinsicHeight(double width) {
1533 1534
    assert(() {
      if (width == null) {
1535 1536 1537 1538 1539
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The width argument to getMinIntrinsicHeight was null.'),
          ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'),
          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.')
        ]);
1540 1541
      }
      if (width < 0.0) {
1542 1543 1544 1545 1546 1547 1548 1549 1550
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The width argument to getMinIntrinsicHeight was negative.'),
          ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'),
          ErrorHint(
            '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.'
          )
        ]);
1551 1552
      }
      return true;
1553
    }());
1554 1555 1556 1557 1558 1559 1560 1561 1562
    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.
1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573
  ///
  /// 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.
  ///
1574 1575
  /// The `width` argument will never be negative or null. It may be infinite.
  ///
1576 1577 1578 1579
  /// 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`.
  ///
1580 1581
  /// This function should never return a negative or infinite value.
  ///
1582 1583
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
1584
  double computeMinIntrinsicHeight(double width) {
1585
    return 0.0;
1586 1587
  }

Adam Barth's avatar
Adam Barth committed
1588
  /// Returns the smallest height beyond which increasing the height never
1589 1590
  /// 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
1591
  ///
1592 1593 1594 1595 1596
  /// 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.
  ///
1597 1598 1599 1600 1601 1602 1603
  /// 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.
  ///
1604
  /// Do not override this method. Instead, implement
1605 1606 1607
  /// [computeMaxIntrinsicHeight].
  @mustCallSuper
  double getMaxIntrinsicHeight(double width) {
1608 1609
    assert(() {
      if (width == null) {
1610 1611 1612 1613 1614
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The width argument to getMaxIntrinsicHeight was null.'),
          ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'),
          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.')
        ]);
1615 1616
      }
      if (width < 0.0) {
1617 1618 1619 1620 1621 1622 1623 1624 1625
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('The width argument to getMaxIntrinsicHeight was negative.'),
          ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'),
          ErrorHint(
            '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.'
          )
        ]);
1626 1627
      }
      return true;
1628
    }());
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638
    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.
1639 1640 1641
  ///
  /// If the layout algorithm is strictly width-in-height-out, or is
  /// width-in-height-out when the height is unconstrained, then this should
1642
  /// return the same value as [computeMinIntrinsicHeight] for the same width.
1643 1644 1645
  ///
  /// Otherwise, the width argument should be ignored, and the returned value
  /// should be equal to or bigger than the value returned by
1646
  /// [computeMinIntrinsicHeight].
1647
  ///
1648 1649
  /// The `width` argument will never be negative or null. It may be infinite.
  ///
1650 1651 1652 1653 1654
  /// 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.
  ///
1655 1656 1657 1658
  /// 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`.
  ///
1659 1660
  /// This function should never return a negative or infinite value.
  ///
1661 1662
  /// See also examples in the definition of [computeMinIntrinsicWidth].
  @protected
1663
  double computeMaxIntrinsicHeight(double width) {
1664
    return 0.0;
1665 1666
  }

1667 1668 1669
  /// Whether this render object has undergone layout and has a [size].
  bool get hasSize => _size != null;

1670
  /// The size of this render box computed during layout.
1671 1672 1673 1674 1675 1676 1677
  ///
  /// 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
1678
  /// of those functions, call [markNeedsLayout] instead to schedule a layout of
1679 1680
  /// the box.
  Size get size {
1681
    assert(hasSize, 'RenderBox was not laid out: ${toString()}');
1682 1683
    assert(() {
      if (_size is _DebugSize) {
Hixie's avatar
Hixie committed
1684
        final _DebugSize _size = this._size;
1685 1686
        assert(_size._owner == this);
        if (RenderObject.debugActiveLayout != null) {
1687 1688 1689 1690 1691
          // 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().
1692 1693 1694
          assert(debugDoingThisResize || debugDoingThisLayout ||
                 (RenderObject.debugActiveLayout == parent && _size._canBeUsedByParent));
        }
Hixie's avatar
Hixie committed
1695
        assert(_size == this._size);
1696 1697
      }
      return true;
1698
    }());
1699 1700 1701
    return _size;
  }
  Size _size;
1702 1703 1704
  /// 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.
1705
  @protected
1706
  set size(Size value) {
1707 1708 1709 1710 1711 1712 1713
    assert(!(debugDoingThisResize && debugDoingThisLayout));
    assert(sizedByParent || !debugDoingThisResize);
    assert(() {
      if ((sizedByParent && debugDoingThisResize) ||
          (!sizedByParent && debugDoingThisLayout))
        return true;
      assert(!debugDoingThisResize);
1714 1715
      final List<DiagnosticsNode> information = <DiagnosticsNode>[];
      information.add(ErrorSummary('RenderBox size setter called incorrectly.'));
1716 1717
      if (debugDoingThisLayout) {
        assert(sizedByParent);
1718
        information.add(ErrorDescription('It appears that the size setter was called from performLayout().'));
1719
      } else {
1720 1721 1722
        information.add(ErrorDescription(
          'The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).'
        ));
1723
        if (owner != null && owner.debugDoingLayout)
1724
          information.add(ErrorDescription('Only the object itself can set its size. It is a contract violation for other objects to set it.'));
1725 1726
      }
      if (sizedByParent)
1727
        information.add(ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().'));
1728
      else
1729 1730
        information.add(ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().'));
      throw FlutterError.fromParts(information);
1731
    }());
1732
    assert(() {
1733
      value = debugAdoptSize(value);
1734
      return true;
1735
    }());
1736
    _size = value;
1737 1738 1739 1740
    assert(() {
      debugAssertDoesMeetConstraints();
      return true;
    }());
1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757
  }

  /// 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;
1758
    assert(() {
1759 1760 1761
      if (value is _DebugSize) {
        if (value._owner != this) {
          if (value._owner.parent != this) {
1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('The size property was assigned a size inappropriately.'),
              describeForError('The following render object'),
              value._owner.describeForError('...was assigned a size obtained from'),
              ErrorDescription(
                '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.'
              ),
              ErrorHint(
                '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.'
              ),
              ErrorHint(
                '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.'
              ),
            ]);
1786 1787
          }
          if (!value._canBeUsedByParent) {
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('A child\'s size was used without setting parentUsesSize.'),
              describeForError('The following render object'),
              value._owner.describeForError('...was assigned a size obtained from its child'),
              ErrorDescription(
                '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.'
              )
            ]);
1801 1802 1803
          }
        }
      }
1804
      result = _DebugSize(value, this, debugCanParentUseSize);
1805
      return true;
1806
    }());
1807
    return result;
1808 1809
  }

1810
  @override
1811
  Rect get semanticBounds => Offset.zero & size;
Hixie's avatar
Hixie committed
1812

1813
  @override
1814 1815 1816 1817 1818
  void debugResetSize() {
    // updates the value of size._canBeUsedByParent if necessary
    size = size;
  }

1819 1820 1821 1822 1823 1824
  Map<TextBaseline, double> _cachedBaselines;
  static bool _debugDoingBaseline = false;
  static bool _debugSetDoingBaseline(bool value) {
    _debugDoingBaseline = value;
    return true;
  }
Adam Barth's avatar
Adam Barth committed
1825 1826 1827 1828 1829 1830 1831 1832

  /// 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
1833
  /// (i.e., the height of the box) unless the caller passes true
Adam Barth's avatar
Adam Barth committed
1834 1835
  /// for `onlyReal`, in which case the function returns null.
  ///
1836 1837 1838
  /// 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.
1839 1840 1841
  ///
  /// When implementing a [RenderBox] subclass, to override the baseline
  /// computation, override [computeDistanceToActualBaseline].
1842
  double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) {
1843
    assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1844
    assert(!debugNeedsLayout);
1845
    assert(() {
1846
      final RenderObject parent = this.parent;
1847
      if (owner.debugDoingLayout)
1848
        return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout;
1849
      if (owner.debugDoingPaint)
1850 1851
        return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
               ((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
1852
      assert(parent == this.parent);
1853
      return false;
1854
    }());
1855
    assert(_debugSetDoingBaseline(true));
1856
    final double result = getDistanceToActualBaseline(baseline);
1857 1858 1859 1860 1861
    assert(_debugSetDoingBaseline(false));
    if (result == null && !onlyReal)
      return size.height;
    return result;
  }
Adam Barth's avatar
Adam Barth committed
1862 1863 1864 1865 1866 1867

  /// 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.
1868
  @protected
1869
  @mustCallSuper
1870
  double getDistanceToActualBaseline(TextBaseline baseline) {
1871
    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1872
    _cachedBaselines ??= <TextBaseline, double>{};
1873 1874 1875
    _cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
    return _cachedBaselines[baseline];
  }
Adam Barth's avatar
Adam Barth committed
1876 1877 1878 1879 1880

  /// 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.
  ///
1881 1882 1883
  /// 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
1884
  ///
1885
  /// Subclasses should override this method to supply the distances to their
1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900
  /// 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).
1901
  @protected
1902
  double computeDistanceToActualBaseline(TextBaseline baseline) {
1903
    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
1904 1905 1906
    return null;
  }

1907
  /// The box constraints most recently received from the parent.
1908
  @override
1909
  BoxConstraints get constraints => super.constraints;
1910 1911

  @override
1912
  void debugAssertDoesMeetConstraints() {
1913
    assert(constraints != null);
1914 1915
    assert(() {
      if (!hasSize) {
1916
        assert(!debugNeedsLayout); // this is called in the size= setter during layout, but in that case we have a size
1917
        DiagnosticsNode contract;
1918
        if (sizedByParent)
1919
          contract = ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().');
1920
        else
1921 1922 1923 1924 1925 1926 1927
          contract = ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().');
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('RenderBox did not set its size during layout.'),
          contract,
          ErrorDescription('It appears that this did not happen; layout completed, but the size property is still null.'),
          DiagnosticsProperty<RenderBox>('The RenderBox in question is', this, style: DiagnosticsTreeStyle.errorProperty)
        ]);
1928
      }
1929
      // verify that the size is not infinite
1930
      if (!_size.isFinite) {
1931 1932 1933 1934 1935 1936 1937 1938
        final List<DiagnosticsNode> information = <DiagnosticsNode>[
          ErrorSummary('$runtimeType object was given an infinite size during layout.'),
          ErrorDescription(
            '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.'
          )
        ];
1939 1940 1941 1942
        if (!constraints.hasBoundedWidth) {
          RenderBox node = this;
          while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
            node = node.parent;
1943 1944

          information.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
1945
        }
1946 1947 1948 1949
        if (!constraints.hasBoundedHeight) {
          RenderBox node = this;
          while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
            node = node.parent;
1950

1951
          information.add(node.describeForError('The nearest ancestor providing an unbounded height constraint is'));
1952
        }
1953 1954 1955 1956 1957 1958
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ...information,
          DiagnosticsProperty<BoxConstraints>('The constraints that applied to the $runtimeType were', constraints, style: DiagnosticsTreeStyle.errorProperty),
          DiagnosticsProperty<Size>('The exact size it was given was', _size, style: DiagnosticsTreeStyle.errorProperty),
          ErrorHint('See https://flutter.dev/docs/development/ui/layout/box-constraints for more information.'),
        ]);
1959
     }
1960 1961
      // verify that the size is within the constraints
      if (!constraints.isSatisfiedBy(_size)) {
1962 1963 1964 1965 1966 1967 1968 1969 1970
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('$runtimeType does not meet its constraints.'),
          DiagnosticsProperty<BoxConstraints>('Constraints', constraints, style: DiagnosticsTreeStyle.errorProperty),
          DiagnosticsProperty<Size>('Size', _size, style: DiagnosticsTreeStyle.errorProperty),
          ErrorHint(
            'If you are not writing your own RenderBox subclass, then this is not '
            'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=BUG.md'
          ),
        ]);
1971
      }
1972
      if (debugCheckIntrinsicSizes) {
1973
        // verify that the intrinsics are sane
1974 1975
        assert(!RenderObject.debugCheckingIntrinsics);
        RenderObject.debugCheckingIntrinsics = true;
1976
        final List<DiagnosticsNode> failures = <DiagnosticsNode>[];
1977 1978 1979 1980

        double testIntrinsic(double function(double extent), String name, double constraint) {
          final double result = function(constraint);
          if (result < 0) {
1981
            failures.add(ErrorDescription(' * $name($constraint) returned a negative value: $result'));
1982 1983
          }
          if (!result.isFinite) {
1984
            failures.add(ErrorDescription(' * $name($constraint) returned a non-finite value: $result'));
1985 1986
          }
          return result;
1987
        }
1988

1989 1990 1991 1992
        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) {
1993
            failures.add(ErrorDescription(' * getMinIntrinsic$name($constraint) returned a larger value ($min) than getMaxIntrinsic$name($constraint) ($max)'));
1994
          }
1995
        }
1996

1997 1998
        testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', double.infinity);
        testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', double.infinity);
1999
        if (constraints.hasBoundedWidth)
2000
          testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', constraints.maxHeight);
2001
        if (constraints.hasBoundedHeight)
2002
          testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', constraints.maxWidth);
2003

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

2006 2007
        RenderObject.debugCheckingIntrinsics = false;
        if (failures.isNotEmpty) {
2008 2009 2010
          // TODO(jacobr): consider nesting the failures object so it is collapsible.
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('The intrinsic dimension methods of the $runtimeType class returned values that violate the intrinsic protocol contract.'),
2011 2012 2013
            ErrorDescription('The following ${failures.length > 1 ? "failures" : "failure"} was detected:'), // should this be tagged as an error or not?
            ...failures,
            ErrorHint(
2014 2015
              '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?template=BUG.md'
2016 2017
            ),
          ]);
2018
        }
2019 2020
      }
      return true;
2021
    }());
2022 2023
  }

2024
  @override
2025
  void markNeedsLayout() {
2026 2027 2028 2029 2030 2031 2032 2033 2034
    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();
2035
      if (parent is RenderObject) {
2036 2037 2038
        markParentNeedsLayout();
        return;
      }
2039 2040 2041
    }
    super.markNeedsLayout();
  }
2042 2043

  @override
2044
  void performResize() {
2045
    // default behavior for subclasses that have sizedByParent = true
2046
    size = constraints.smallest;
2047
    assert(size.isFinite);
2048
  }
2049 2050

  @override
2051
  void performLayout() {
2052 2053
    assert(() {
      if (!sizedByParent) {
2054 2055 2056 2057 2058 2059 2060 2061
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('$runtimeType did not implement performLayout().'),
          ErrorHint(
            '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.'
          )
        ]);
2062 2063
      }
      return true;
2064
    }());
2065 2066
  }

2067
  /// Determines the set of render objects located at the given position.
Adam Barth's avatar
Adam Barth committed
2068
  ///
2069 2070 2071 2072
  /// 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
2073
  ///
2074 2075 2076 2077
  /// 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.
2078
  ///
2079 2080 2081 2082 2083
  /// If transforming is necessary, [HitTestResult.addWithPaintTransform],
  /// [HitTestResult.addWithPaintOffset], or [HitTestResult.addWithRawTransform] need
  /// to be invoked by the caller to record the required transform operations
  /// in the [HitTestResult]. These methods will also help with applying the
  /// transform to `position`.
2084
  ///
2085 2086 2087 2088 2089 2090
  /// 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.
2091
  bool hitTest(BoxHitTestResult result, { @required Offset position }) {
2092 2093
    assert(() {
      if (!hasSize) {
2094
        if (debugNeedsLayout) {
2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('Cannot hit test a render box that has never been laid out.'),
            describeForError('The hitTest() method was called on this RenderBox'),
            ErrorDescription(
              '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.'
            ),
            ErrorHint(
              '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).'
            )
          ]);
2110
        }
2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Cannot hit test a render box with no size.'),
          describeForError('The hitTest() method was called on this RenderBox'),
          ErrorDescription(
            'Although this node is not marked as needing layout, '
            'its size is not set.'
          ),
          ErrorHint(
            '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.'
          ),
        ]);
2124 2125
      }
      return true;
2126
    }());
2127
    if (_size.contains(position)) {
Adam Barth's avatar
Adam Barth committed
2128
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
2129
        result.add(BoxHitTestEntry(this, position));
Adam Barth's avatar
Adam Barth committed
2130 2131
        return true;
      }
2132 2133
    }
    return false;
2134
  }
Adam Barth's avatar
Adam Barth committed
2135

2136
  /// Override this method if this render object can be hit even if its
2137
  /// children were not hit.
2138
  ///
2139 2140 2141 2142 2143
  /// 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.
  ///
2144 2145
  /// Used by [hitTest]. If you override [hitTest] and do not call this
  /// function, then you don't need to implement this function.
2146
  @protected
2147
  bool hitTestSelf(Offset position) => false;
Adam Barth's avatar
Adam Barth committed
2148

2149
  /// Override this method to check whether any children are located at the
2150
  /// given position.
Adam Barth's avatar
Adam Barth committed
2151
  ///
2152
  /// Typically children should be hit-tested in reverse paint order so that
Adam Barth's avatar
Adam Barth committed
2153 2154
  /// hit tests at locations where children overlap hit the child that is
  /// visually "on top" (i.e., paints later).
2155
  ///
2156 2157 2158 2159 2160
  /// 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.
  ///
2161 2162 2163 2164 2165
  /// If transforming is necessary, [HitTestResult.addWithPaintTransform],
  /// [HitTestResult.addWithPaintOffset], or [HitTestResult.addWithRawTransform] need
  /// to be invoked by the caller to record the required transform operations
  /// in the [HitTestResult]. These methods will also help with applying the
  /// transform to `position`.
2166
  ///
2167 2168
  /// Used by [hitTest]. If you override [hitTest] and do not call this
  /// function, then you don't need to implement this function.
2169
  @protected
2170
  bool hitTestChildren(BoxHitTestResult result, { Offset position }) => false;
2171

Adam Barth's avatar
Adam Barth committed
2172
  /// Multiply the transform from the parent's coordinate system to this box's
2173
  /// coordinate system into the given transform.
Adam Barth's avatar
Adam Barth committed
2174 2175 2176 2177
  ///
  /// 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.
2178
  ///
2179
  /// The [RenderBox] implementation takes care of adjusting the matrix for the
2180 2181
  /// position of the given child as determined during layout and stored on the
  /// child's [parentData] in the [BoxParentData.offset] field.
2182
  @override
2183
  void applyPaintTransform(RenderObject child, Matrix4 transform) {
2184
    assert(child != null);
2185
    assert(child.parent == this);
2186 2187
    assert(() {
      if (child.parentData is! BoxParentData) {
2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('$runtimeType does not implement applyPaintTransform.'),
          describeForError('The following $runtimeType object'),
          child.describeForError('...did not use a BoxParentData class for the parentData field of the following child'),
          ErrorDescription('The $runtimeType class inherits from RenderBox.'),
          ErrorHint(
            '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}).'
         )
        ]);
2201 2202
      }
      return true;
2203
    }());
2204 2205
    final BoxParentData childParentData = child.parentData;
    final Offset offset = childParentData.offset;
2206
    transform.translate(offset.dx, offset.dy);
2207 2208
  }

2209
  /// Convert the given point from the global coordinate system in logical pixels
2210
  /// to the local coordinate system for this box.
2211
  ///
2212 2213 2214
  /// This method will un-project the point from the screen onto the widget,
  /// which makes it different from [MatrixUtils.transformPoint].
  ///
2215
  /// If the transform from global coordinates to local coordinates is
2216
  /// degenerate, this function returns [Offset.zero].
2217 2218 2219 2220
  ///
  /// 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.
2221 2222
  ///
  /// This method is implemented in terms of [getTransformTo].
2223
  Offset globalToLocal(Offset point, { RenderObject ancestor }) {
2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235
    // We want to find point (p) that corresponds to a given point on the
    // screen (s), but that also physically resides on the local render plane,
    // so that it is useful for visually accurate gesture processing in the
    // local space. For that, we can't simply transform 2D screen point to
    // the 3D local space since the screen space lacks the depth component |z|,
    // and so there are many 3D points that correspond to the screen point.
    // We must first unproject the screen point onto the render plane to find
    // the true 3D point that corresponds to the screen point.
    // We do orthogonal unprojection after undoing perspective, in local space.
    // The render plane is specified by renderBox offset (o) and Z axis (n).
    // Unprojection is done by finding the intersection of the view vector (d)
    // with the local X-Y plane: (o-s).dot(n) == (p-s).dot(n), (p-s) == |z|*d.
2236
    final Matrix4 transform = getTransformTo(ancestor);
2237
    final double det = transform.invert();
2238
    if (det == 0.0)
2239
      return Offset.zero;
2240 2241 2242 2243 2244 2245
    final Vector3 n = Vector3(0.0, 0.0, 1.0);
    final Vector3 i = transform.perspectiveTransform(Vector3(0.0, 0.0, 0.0));
    final Vector3 d = transform.perspectiveTransform(Vector3(0.0, 0.0, 1.0)) - i;
    final Vector3 s = transform.perspectiveTransform(Vector3(point.dx, point.dy, 0.0));
    final Vector3 p = s - d * (n.dot(s) / n.dot(d));
    return Offset(p.x, p.y);
2246 2247
  }

2248
  /// Convert the given point from the local coordinate system for this box to
2249
  /// the global coordinate system in logical pixels.
2250 2251 2252 2253
  ///
  /// 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.
2254 2255
  ///
  /// This method is implemented in terms of [getTransformTo].
2256
  Offset localToGlobal(Offset point, { RenderObject ancestor }) {
2257
    return MatrixUtils.transformPoint(getTransformTo(ancestor), point);
2258 2259
  }

2260
  /// Returns a rectangle that contains all the pixels painted by this box.
Adam Barth's avatar
Adam Barth committed
2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272
  ///
  /// 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.
2273
  @override
2274
  Rect get paintBounds => Offset.zero & size;
Adam Barth's avatar
Adam Barth committed
2275

2276
  /// Override this method to handle pointer events that hit this render object.
2277 2278 2279 2280
  ///
  /// 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.)
2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291
  ///
  /// 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 ...
  /// }
  /// ```
2292
  @override
2293
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
2294
    super.handleEvent(event, entry);
2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314
  }

  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) {
2315 2316
    assert(() {
      if (debugPaintPointersEnabled) {
Ian Hickson's avatar
Ian Hickson committed
2317
        if (event is PointerDownEvent) {
2318
          _debugActivePointers += 1;
Ian Hickson's avatar
Ian Hickson committed
2319
        } else if (event is PointerUpEvent || event is PointerCancelEvent) {
2320
          _debugActivePointers -= 1;
Ian Hickson's avatar
Ian Hickson committed
2321
        }
2322 2323 2324
        markNeedsPaint();
      }
      return true;
2325
    }());
2326
    return true;
2327 2328
  }

2329
  @override
2330
  void debugPaint(PaintingContext context, Offset offset) {
2331 2332 2333 2334 2335 2336 2337 2338
    assert(() {
      if (debugPaintSizeEnabled)
        debugPaintSize(context, offset);
      if (debugPaintBaselinesEnabled)
        debugPaintBaselines(context, offset);
      if (debugPaintPointersEnabled)
        debugPaintPointers(context, offset);
      return true;
2339
    }());
2340
  }
2341 2342 2343 2344

  /// In debug mode, paints a border around this render box.
  ///
  /// Called for every [RenderBox] when [debugPaintSizeEnabled] is true.
2345
  @protected
2346
  void debugPaintSize(PaintingContext context, Offset offset) {
2347
    assert(() {
2348
      final Paint paint = Paint()
2349
       ..style = PaintingStyle.stroke
2350
       ..strokeWidth = 1.0
2351
       ..color = const Color(0xFF00FFFF);
2352 2353
      context.canvas.drawRect((offset & size).deflate(0.5), paint);
      return true;
2354
    }());
2355
  }
2356 2357 2358 2359

  /// In debug mode, paints a line for each baseline.
  ///
  /// Called for every [RenderBox] when [debugPaintBaselinesEnabled] is true.
2360
  @protected
2361
  void debugPaintBaselines(PaintingContext context, Offset offset) {
2362
    assert(() {
2363
      final Paint paint = Paint()
2364
       ..style = PaintingStyle.stroke
2365 2366 2367
       ..strokeWidth = 0.25;
      Path path;
      // ideographic baseline
2368
      final double baselineI = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true);
2369
      if (baselineI != null) {
2370
        paint.color = const Color(0xFFFFD000);
2371
        path = Path();
2372 2373 2374 2375 2376
        path.moveTo(offset.dx, offset.dy + baselineI);
        path.lineTo(offset.dx + size.width, offset.dy + baselineI);
        context.canvas.drawPath(path, paint);
      }
      // alphabetic baseline
2377
      final double baselineA = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true);
2378
      if (baselineA != null) {
2379
        paint.color = const Color(0xFF00FF00);
2380
        path = Path();
2381 2382 2383 2384 2385
        path.moveTo(offset.dx, offset.dy + baselineA);
        path.lineTo(offset.dx + size.width, offset.dy + baselineA);
        context.canvas.drawPath(path, paint);
      }
      return true;
2386
    }());
2387
  }
2388

2389 2390
  /// In debug mode, paints a rectangle if this render box has counted more
  /// pointer downs than pointer up events.
2391 2392
  ///
  /// Called for every [RenderBox] when [debugPaintPointersEnabled] is true.
2393 2394 2395
  ///
  /// By default, events are not counted. For details on how to ensure that
  /// events are counted for your class, see [debugHandleEvent].
2396
  @protected
2397
  void debugPaintPointers(PaintingContext context, Offset offset) {
2398 2399
    assert(() {
      if (_debugActivePointers > 0) {
2400 2401
        final Paint paint = Paint()
         ..color = Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000));
2402 2403 2404
        context.canvas.drawRect(offset & size, paint);
      }
      return true;
2405
    }());
2406
  }
2407

2408
  @override
2409 2410
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2411
    properties.add(DiagnosticsProperty<Size>('size', _size, missingIfNull: true));
2412
  }
2413 2414
}

2415 2416
/// A mixin that provides useful default behaviors for boxes with children
/// managed by the [ContainerRenderObjectMixin] mixin.
Adam Barth's avatar
Adam Barth committed
2417 2418 2419 2420
///
/// By convention, this class doesn't override any members of the superclass.
/// Instead, it provides helpful functions that subclasses can call as
/// appropriate.
2421
mixin RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerBoxParentData<ChildType>> implements ContainerRenderObjectMixin<ChildType, ParentDataType> {
2422
  /// Returns the baseline of the first child with a baseline.
Adam Barth's avatar
Adam Barth committed
2423 2424 2425
  ///
  /// Useful when the children are displayed vertically in the same order they
  /// appear in the child list.
2426
  double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) {
2427
    assert(!debugNeedsLayout);
Adam Barth's avatar
Adam Barth committed
2428
    ChildType child = firstChild;
2429
    while (child != null) {
Hixie's avatar
Hixie committed
2430
      final ParentDataType childParentData = child.parentData;
2431
      final double result = child.getDistanceToActualBaseline(baseline);
2432
      if (result != null)
2433
        return result + childParentData.offset.dy;
Hixie's avatar
Hixie committed
2434
      child = childParentData.nextSibling;
2435 2436 2437 2438
    }
    return null;
  }

2439
  /// Returns the minimum baseline value among every child.
Adam Barth's avatar
Adam Barth committed
2440 2441 2442
  ///
  /// Useful when the vertical position of the children isn't determined by the
  /// order in the child list.
2443
  double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
2444
    assert(!debugNeedsLayout);
2445
    double result;
Adam Barth's avatar
Adam Barth committed
2446
    ChildType child = firstChild;
2447
    while (child != null) {
Hixie's avatar
Hixie committed
2448
      final ParentDataType childParentData = child.parentData;
2449 2450
      double candidate = child.getDistanceToActualBaseline(baseline);
      if (candidate != null) {
2451
        candidate += childParentData.offset.dy;
2452 2453 2454 2455 2456
        if (result != null)
          result = math.min(result, candidate);
        else
          result = candidate;
      }
Hixie's avatar
Hixie committed
2457
      child = childParentData.nextSibling;
2458 2459 2460 2461
    }
    return result;
  }

2462
  /// Performs a hit test on each child by walking the child list backwards.
Adam Barth's avatar
Adam Barth committed
2463 2464 2465
  ///
  /// Stops walking once after the first child reports that it contains the
  /// given point. Returns whether any children contain the given point.
2466 2467 2468 2469 2470
  ///
  /// See also:
  ///
  ///  * [defaultPaint], which paints the children appropriate for this
  ///    hit-testing strategy.
2471
  bool defaultHitTestChildren(BoxHitTestResult result, { Offset position }) {
2472 2473 2474
    // 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
2475
      final ParentDataType childParentData = child.parentData;
2476 2477 2478 2479 2480 2481 2482 2483 2484
      final bool isHit = result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child.hitTest(result, position: transformed);
        },
      );
      if (isHit)
2485
        return true;
Hixie's avatar
Hixie committed
2486
      child = childParentData.previousSibling;
2487
    }
2488
    return false;
2489 2490
  }

2491
  /// Paints each child by walking the child list forwards.
2492 2493 2494 2495 2496
  ///
  /// See also:
  ///
  ///  * [defaultHitTestChildren], which implements hit-testing of the children
  ///    in a manner appropriate for this painting strategy.
2497
  void defaultPaint(PaintingContext context, Offset offset) {
Adam Barth's avatar
Adam Barth committed
2498
    ChildType child = firstChild;
2499
    while (child != null) {
Hixie's avatar
Hixie committed
2500
      final ParentDataType childParentData = child.parentData;
Adam Barth's avatar
Adam Barth committed
2501
      context.paintChild(child, childParentData.offset + offset);
Hixie's avatar
Hixie committed
2502
      child = childParentData.nextSibling;
2503 2504
    }
  }
Adam Barth's avatar
Adam Barth committed
2505

2506 2507 2508 2509 2510
  /// 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
2511
  List<ChildType> getChildrenAsList() {
2512
    final List<ChildType> result = <ChildType>[];
Adam Barth's avatar
Adam Barth committed
2513 2514 2515 2516 2517 2518 2519 2520
    RenderBox child = firstChild;
    while (child != null) {
      final ParentDataType childParentData = child.parentData;
      result.add(child);
      child = childParentData.nextSibling;
    }
    return result;
  }
2521
}