shifted_box.dart 48.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:math' as math;
6

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

9
import 'box.dart';
10
import 'debug.dart';
11
import 'debug_overflow_indicator.dart';
12
import 'layer.dart';
13
import 'object.dart';
14
import 'stack.dart' show RelativeRect;
15

16 17 18 19 20 21 22 23
/// Signature for a function that transforms a [BoxConstraints] to another
/// [BoxConstraints].
///
/// Used by [RenderConstraintsTransformBox] and [ConstraintsTransformBox].
/// Typically the caller requires the returned [BoxConstraints] to be
/// [BoxConstraints.isNormalized].
typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints);

24 25
/// Abstract class for one-child-layout render boxes that provide control over
/// the child's position.
26
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
27
  /// Initializes the [child] property for subclasses.
28
  RenderShiftedBox(RenderBox? child) {
29 30 31
    this.child = child;
  }

32
  @override
33
  double computeMinIntrinsicWidth(double height) {
34
    return child?.getMinIntrinsicWidth(height) ?? 0.0;
35 36
  }

37
  @override
38
  double computeMaxIntrinsicWidth(double height) {
39
    return child?.getMaxIntrinsicWidth(height) ?? 0.0;
40 41
  }

42
  @override
43
  double computeMinIntrinsicHeight(double width) {
44
    return child?.getMinIntrinsicHeight(width) ?? 0.0;
45 46
  }

47
  @override
48
  double computeMaxIntrinsicHeight(double width) {
49
    return child?.getMaxIntrinsicHeight(width) ?? 0.0;
50 51
  }

52
  @override
53 54
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
    double? result;
55
    final RenderBox? child = this.child;
56
    if (child != null) {
57
      assert(!debugNeedsLayout);
58 59
      result = child.getDistanceToActualBaseline(baseline);
      final BoxParentData childParentData = child.parentData! as BoxParentData;
60
      if (result != null) {
61
        result += childParentData.offset.dy;
62
      }
63 64 65 66 67 68
    } else {
      result = super.computeDistanceToActualBaseline(baseline);
    }
    return result;
  }

69
  @override
70
  void paint(PaintingContext context, Offset offset) {
71
    final RenderBox? child = this.child;
Hixie's avatar
Hixie committed
72
    if (child != null) {
73 74
      final BoxParentData childParentData = child.parentData! as BoxParentData;
      context.paintChild(child, childParentData.offset + offset);
Hixie's avatar
Hixie committed
75
    }
76 77
  }

78
  @override
79
  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
80
    final RenderBox? child = this.child;
81
    if (child != null) {
82
      final BoxParentData childParentData = child.parentData! as BoxParentData;
83 84 85
      return result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
86
        hitTest: (BoxHitTestResult result, Offset transformed) {
87
          assert(transformed == position - childParentData.offset);
88
          return child.hitTest(result, position: transformed);
89 90
        },
      );
91
    }
Adam Barth's avatar
Adam Barth committed
92
    return false;
93 94 95
  }
}

96 97 98 99 100 101
/// Insets its child by the given padding.
///
/// When passing layout constraints to its child, padding shrinks the
/// constraints by the given padding, causing the child to layout at a smaller
/// size. Padding then sizes itself to its child's size, inflated by the
/// padding, effectively creating empty space around the child.
102
class RenderPadding extends RenderShiftedBox {
103 104 105
  /// Creates a render object that insets its child.
  ///
  /// The [padding] argument must not be null and must have non-negative insets.
106
  RenderPadding({
107 108 109
    required EdgeInsetsGeometry padding,
    TextDirection? textDirection,
    RenderBox? child,
110
  }) : assert(padding.isNonNegative),
111
       _textDirection = textDirection,
112
       _padding = padding,
Ian Hickson's avatar
Ian Hickson committed
113
       super(child);
114

115
  EdgeInsets? _resolvedPadding;
116

Ian Hickson's avatar
Ian Hickson committed
117
  void _resolve() {
118
    if (_resolvedPadding != null) {
Ian Hickson's avatar
Ian Hickson committed
119
      return;
120
    }
Ian Hickson's avatar
Ian Hickson committed
121
    _resolvedPadding = padding.resolve(textDirection);
122
    assert(_resolvedPadding!.isNonNegative);
Ian Hickson's avatar
Ian Hickson committed
123 124 125 126 127
  }

  void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
128
  }
129

130
  /// The amount to pad the child in each dimension.
131 132
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
133
  /// must not be null.
134 135 136
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;
  set padding(EdgeInsetsGeometry value) {
137
    assert(value.isNonNegative);
138
    if (_padding == value) {
139
      return;
140
    }
141
    _padding = value;
Ian Hickson's avatar
Ian Hickson committed
142
    _markNeedResolution();
143 144 145
  }

  /// The text direction with which to resolve [padding].
Ian Hickson's avatar
Ian Hickson committed
146 147 148
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
149 150 151
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
152
    if (_textDirection == value) {
153
      return;
154
    }
155
    _textDirection = value;
Ian Hickson's avatar
Ian Hickson committed
156
    _markNeedResolution();
157 158
  }

159
  @override
160
  double computeMinIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
161
    _resolve();
162 163
    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
164 165
    if (child != null) {
      // Relies on double.infinity absorption.
166
      return child!.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
167
    }
168
    return totalHorizontalPadding;
169 170
  }

171
  @override
172
  double computeMaxIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
173
    _resolve();
174 175
    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
176 177
    if (child != null) {
      // Relies on double.infinity absorption.
178
      return child!.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
179
    }
180
    return totalHorizontalPadding;
181 182
  }

183
  @override
184
  double computeMinIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
185
    _resolve();
186 187
    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
188 189
    if (child != null) {
      // Relies on double.infinity absorption.
190
      return child!.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
191
    }
192
    return totalVerticalPadding;
193 194
  }

195
  @override
196
  double computeMaxIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
197
    _resolve();
198 199
    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
200 201
    if (child != null) {
      // Relies on double.infinity absorption.
202
      return child!.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
203
    }
204
    return totalVerticalPadding;
205 206
  }

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    _resolve();
    assert(_resolvedPadding != null);
    if (child == null) {
      return constraints.constrain(Size(
        _resolvedPadding!.left + _resolvedPadding!.right,
        _resolvedPadding!.top + _resolvedPadding!.bottom,
      ));
    }
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
    final Size childSize = child!.getDryLayout(innerConstraints);
    return constraints.constrain(Size(
      _resolvedPadding!.left + childSize.width + _resolvedPadding!.right,
      _resolvedPadding!.top + childSize.height + _resolvedPadding!.bottom,
    ));
  }

225
  @override
226
  void performLayout() {
227
    final BoxConstraints constraints = this.constraints;
Ian Hickson's avatar
Ian Hickson committed
228
    _resolve();
229
    assert(_resolvedPadding != null);
230
    if (child == null) {
231
      size = constraints.constrain(Size(
232 233
        _resolvedPadding!.left + _resolvedPadding!.right,
        _resolvedPadding!.top + _resolvedPadding!.bottom,
234 235 236
      ));
      return;
    }
237 238
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
    child!.layout(innerConstraints, parentUsesSize: true);
239
    final BoxParentData childParentData = child!.parentData! as BoxParentData;
240
    childParentData.offset = Offset(_resolvedPadding!.left, _resolvedPadding!.top);
241
    size = constraints.constrain(Size(
242 243
      _resolvedPadding!.left + child!.size.width + _resolvedPadding!.right,
      _resolvedPadding!.top + child!.size.height + _resolvedPadding!.bottom,
244 245 246
    ));
  }

247
  @override
248 249 250
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
251
      final Rect outerRect = offset & size;
252
      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding!.deflateRect(outerRect) : null);
253
      return true;
254
    }());
255 256
  }

257
  @override
258 259
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
260 261
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
262
  }
263 264
}

265
/// Abstract class for one-child-layout render boxes that use a
266
/// [AlignmentGeometry] to align their children.
267
abstract class RenderAligningShiftedBox extends RenderShiftedBox {
268 269 270
  /// Initializes member variables for subclasses.
  ///
  /// The [alignment] argument must not be null.
271 272 273
  ///
  /// The [textDirection] must be non-null if the [alignment] is
  /// direction-sensitive.
274
  RenderAligningShiftedBox({
275
    AlignmentGeometry alignment = Alignment.center,
276 277
    required TextDirection? textDirection,
    RenderBox? child,
278
  }) : _alignment = alignment,
279
       _textDirection = textDirection,
Ian Hickson's avatar
Ian Hickson committed
280
       super(child);
281

282
  Alignment? _resolvedAlignment;
283

Ian Hickson's avatar
Ian Hickson committed
284
  void _resolve() {
285
    if (_resolvedAlignment != null) {
Ian Hickson's avatar
Ian Hickson committed
286
      return;
287
    }
Ian Hickson's avatar
Ian Hickson committed
288 289 290 291 292 293
    _resolvedAlignment = alignment.resolve(textDirection);
  }

  void _markNeedResolution() {
    _resolvedAlignment = null;
    markNeedsLayout();
294
  }
295

296 297 298
  /// How to align the child.
  ///
  /// The x and y values of the alignment control the horizontal and vertical
299
  /// alignment, respectively. An x value of -1.0 means that the left edge of
300 301 302
  /// the child is aligned with the left edge of the parent whereas an x value
  /// of 1.0 means that the right edge of the child is aligned with the right
  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
303
  /// For example, a value of 0.0 means that the center of the child is aligned
304
  /// with the center of the parent.
305
  ///
306
  /// If this is set to an [AlignmentDirectional] object, then
307
  /// [textDirection] must not be null.
308 309
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
310 311
  /// Sets the alignment to a new value, and triggers a layout update.
  ///
Ian Hickson's avatar
Ian Hickson committed
312
  /// The new alignment must not be null.
313
  set alignment(AlignmentGeometry value) {
314
    if (_alignment == value) {
315
      return;
316
    }
317
    _alignment = value;
Ian Hickson's avatar
Ian Hickson committed
318
    _markNeedResolution();
319 320 321
  }

  /// The text direction with which to resolve [alignment].
Ian Hickson's avatar
Ian Hickson committed
322 323 324
  ///
  /// This may be changed to null, but only after [alignment] has been changed
  /// to a value that does not depend on the direction.
325 326 327
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
328
    if (_textDirection == value) {
329
      return;
330
    }
331
    _textDirection = value;
Ian Hickson's avatar
Ian Hickson committed
332
    _markNeedResolution();
333 334
  }

335 336 337 338 339 340 341 342
  /// Apply the current [alignment] to the [child].
  ///
  /// Subclasses should call this method if they have a child, to have
  /// this class perform the actual alignment. If there is no child,
  /// do not call this method.
  ///
  /// This method must be called after the child has been laid out and
  /// this object's own size has been set.
343
  @protected
344
  void alignChild() {
Ian Hickson's avatar
Ian Hickson committed
345
    _resolve();
346
    assert(child != null);
347 348
    assert(!child!.debugNeedsLayout);
    assert(child!.hasSize);
349
    assert(hasSize);
Ian Hickson's avatar
Ian Hickson committed
350
    assert(_resolvedAlignment != null);
351
    final BoxParentData childParentData = child!.parentData! as BoxParentData;
352
    childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
353 354 355
  }

  @override
356 357
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
358 359
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
360 361 362
  }
}

363
/// Positions its child using an [AlignmentGeometry].
364 365 366
///
/// For example, to align a box at the bottom right, you would pass this box a
/// tight constraint that is bigger than the child's natural size,
367
/// with an alignment of [Alignment.bottomRight].
368 369 370 371 372 373
///
/// By default, sizes to be as big as possible in both axes. If either axis is
/// unconstrained, then in that direction it will be sized to fit the child's
/// dimensions. Using widthFactor and heightFactor you can force this latter
/// behavior in all cases.
class RenderPositionedBox extends RenderAligningShiftedBox {
374
  /// Creates a render object that positions its child.
375
  RenderPositionedBox({
376
    super.child,
377 378
    double? widthFactor,
    double? heightFactor,
379 380
    super.alignment,
    super.textDirection,
381 382 383
  }) : assert(widthFactor == null || widthFactor >= 0.0),
       assert(heightFactor == null || heightFactor >= 0.0),
       _widthFactor = widthFactor,
384
       _heightFactor = heightFactor;
385

386
  /// If non-null, sets its width to the child's width multiplied by this factor.
387 388
  ///
  /// Can be both greater and less than 1.0 but must be positive.
389 390 391
  double? get widthFactor => _widthFactor;
  double? _widthFactor;
  set widthFactor(double? value) {
392
    assert(value == null || value >= 0.0);
393
    if (_widthFactor == value) {
394
      return;
395
    }
396
    _widthFactor = value;
397 398 399
    markNeedsLayout();
  }

400
  /// If non-null, sets its height to the child's height multiplied by this factor.
401 402
  ///
  /// Can be both greater and less than 1.0 but must be positive.
403 404 405
  double? get heightFactor => _heightFactor;
  double? _heightFactor;
  set heightFactor(double? value) {
406
    assert(value == null || value >= 0.0);
407
    if (_heightFactor == value) {
408
      return;
409
    }
410 411 412
    _heightFactor = value;
    markNeedsLayout();
  }
413

414 415 416 417 418 419 420 421
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
    if (child != null) {
      final Size childSize = child!.getDryLayout(constraints.loosen());
      return constraints.constrain(Size(
        shrinkWrapWidth ? childSize.width * (_widthFactor ?? 1.0) : double.infinity,
422 423
        shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity,
      ));
424 425 426 427 428 429 430
    }
    return constraints.constrain(Size(
      shrinkWrapWidth ? 0.0 : double.infinity,
      shrinkWrapHeight ? 0.0 : double.infinity,
    ));
  }

431
  @override
432
  void performLayout() {
433
    final BoxConstraints constraints = this.constraints;
434 435
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
436

437
    if (child != null) {
438
      child!.layout(constraints.loosen(), parentUsesSize: true);
439 440 441 442
      size = constraints.constrain(Size(
        shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
        shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
      ));
443
      alignChild();
444
    } else {
445 446 447 448
      size = constraints.constrain(Size(
        shrinkWrapWidth ? 0.0 : double.infinity,
        shrinkWrapHeight ? 0.0 : double.infinity,
      ));
449 450 451
    }
  }

452
  @override
453 454 455
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
456
      final Paint paint;
457
      if (child != null && !child!.size.isEmpty) {
458
        final Path path;
459
        paint = Paint()
460
          ..style = PaintingStyle.stroke
461
          ..strokeWidth = 1.0
462
          ..color = const Color(0xFFFFFF00);
463
        path = Path();
464
        final BoxParentData childParentData = child!.parentData! as BoxParentData;
465 466
        if (childParentData.offset.dy > 0.0) {
          // vertical alignment arrows
467
          final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
          path
            ..moveTo(offset.dx + size.width / 2.0, offset.dy)
            ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
            ..relativeLineTo(headSize, 0.0)
            ..relativeLineTo(-headSize, headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(headSize, 0.0)
            ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
            ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
            ..relativeLineTo(headSize, 0.0)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(-headSize, headSize)
            ..relativeLineTo(headSize, 0.0);
          context.canvas.drawPath(path, paint);
        }
        if (childParentData.offset.dx > 0.0) {
          // horizontal alignment arrows
485
          final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
          path
            ..moveTo(offset.dx, offset.dy + size.height / 2.0)
            ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
            ..relativeLineTo(0.0, headSize)
            ..relativeLineTo(headSize, -headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(0.0, headSize)
            ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
            ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
            ..relativeLineTo(0.0, headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(headSize, -headSize)
            ..relativeLineTo(0.0, headSize);
          context.canvas.drawPath(path, paint);
        }
      } else {
502
        paint = Paint()
503
          ..color = const Color(0x90909090);
504 505 506
        context.canvas.drawRect(offset & size, paint);
      }
      return true;
507
    }());
508 509
  }

510
  @override
511 512
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
513 514
    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
515
  }
516 517
}

518 519 520 521 522 523 524 525 526 527 528 529 530
/// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent.
///
/// A render overflow box proxies most functions in the render box protocol to
/// its child, except that when laying out its child, it passes constraints
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
/// just passing the parent's constraints in. Specifically, it overrides any of
/// the equivalent fields on the constraints given by the parent with the
/// constraints given by these fields for each such field that is not null. It
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
/// ignoring the child's dimensions.
///
/// For example, if you wanted a box to always render 50 pixels high, regardless
531 532 533 534
/// of where it was rendered, you would wrap it in a
/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
/// Generally speaking, to avoid confusing behavior around hit testing, a
/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
535
///
536
/// The child is positioned according to [alignment]. To position a smaller
537
/// child inside a larger parent, use [RenderPositionedBox] and
538
/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
539 540 541
///
/// See also:
///
542 543 544
///  * [RenderConstraintsTransformBox] for a render object that applies an
///    arbitrary transform to its constraints before sizing its child using
///    the new constraints, treating any overflow as error.
545 546 547
///  * [RenderSizedOverflowBox], a render object that is a specific size but
///    passes its original constraints through to its child, which it allows to
///    overflow.
548
class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
549
  /// Creates a render object that lets its child overflow itself.
550
  RenderConstrainedOverflowBox({
551
    super.child,
552 553 554 555
    double? minWidth,
    double? maxWidth,
    double? minHeight,
    double? maxHeight,
556 557
    super.alignment,
    super.textDirection,
558 559 560
  }) : _minWidth = minWidth,
       _maxWidth = maxWidth,
       _minHeight = minHeight,
561
       _maxHeight = maxHeight;
562 563 564

  /// The minimum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
565 566 567
  double? get minWidth => _minWidth;
  double? _minWidth;
  set minWidth(double? value) {
568
    if (_minWidth == value) {
569
      return;
570
    }
571 572 573 574 575 576
    _minWidth = value;
    markNeedsLayout();
  }

  /// The maximum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
577 578 579
  double? get maxWidth => _maxWidth;
  double? _maxWidth;
  set maxWidth(double? value) {
580
    if (_maxWidth == value) {
581
      return;
582
    }
583 584 585 586 587 588
    _maxWidth = value;
    markNeedsLayout();
  }

  /// The minimum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
589 590 591
  double? get minHeight => _minHeight;
  double? _minHeight;
  set minHeight(double? value) {
592
    if (_minHeight == value) {
593
      return;
594
    }
595 596 597 598 599 600
    _minHeight = value;
    markNeedsLayout();
  }

  /// The maximum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
601 602 603
  double? get maxHeight => _maxHeight;
  double? _maxHeight;
  set maxHeight(double? value) {
604
    if (_maxHeight == value) {
605
      return;
606
    }
607 608 609 610 611
    _maxHeight = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
612
    return BoxConstraints(
613 614 615
      minWidth: _minWidth ?? constraints.minWidth,
      maxWidth: _maxWidth ?? constraints.maxWidth,
      minHeight: _minHeight ?? constraints.minHeight,
616
      maxHeight: _maxHeight ?? constraints.maxHeight,
617 618 619
    );
  }

620
  @override
621 622
  bool get sizedByParent => true;

623
  @override
624 625
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.biggest;
626 627
  }

628
  @override
629 630
  void performLayout() {
    if (child != null) {
631
      child?.layout(_getInnerConstraints(constraints), parentUsesSize: true);
632
      alignChild();
633 634 635
    }
  }

636
  @override
637 638
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
639 640 641 642
    properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
    properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
    properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
    properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
643 644 645
  }
}

646 647 648
/// A [RenderBox] that applies an arbitrary transform to its constraints,
/// and sizes its child using the resulting [BoxConstraints], optionally
/// clipping, or treating the overflow as an error.
649
///
650 651 652 653 654 655 656
/// This [RenderBox] sizes its child using a [BoxConstraints] created by
/// applying [constraintsTransform] to this [RenderBox]'s own [constraints].
/// This box will then attempt to adopt the same size, within the limits of its
/// own constraints. If it ends up with a different size, it will align the
/// child based on [alignment]. If the box cannot expand enough to accommodate
/// the entire child, the child will be clipped if [clipBehavior] is not
/// [Clip.none].
657
///
658 659 660
/// In debug mode, if [clipBehavior] is [Clip.none] and the child overflows the
/// container, a warning will be printed on the console, and black and yellow
/// striped areas will appear where the overflow occurs.
661
///
662 663 664
/// When [child] is null, this [RenderBox] takes the smallest possible size and
/// never overflows.
///
665
/// This [RenderBox] can be used to ensure some of [child]'s natural dimensions
666 667 668 669 670 671 672
/// are honored, and get an early warning during development otherwise. For
/// instance, if [child] requires a minimum height to fully display its content,
/// [constraintsTransform] can be set to a function that removes the `maxHeight`
/// constraint from the incoming [BoxConstraints], so that if the parent
/// [RenderObject] fails to provide enough vertical space, a warning will be
/// displayed in debug mode, while still allowing [child] to grow vertically.
///
673 674
/// See also:
///
675 676
///  * [ConstraintsTransformBox], the widget that makes use of this
///    [RenderObject] and exposes the same functionality.
677 678 679
///  * [RenderConstrainedBox], which renders a box which imposes constraints
///    on its child.
///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
680 681
///    constraints on its child than it gets from its parent, possibly allowing
///    the child to overflow the parent.
682 683 684
///  * [RenderConstraintsTransformBox] for a render object that applies an
///    arbitrary transform to its constraints before sizing its child using
///    the new constraints, treating any overflow as error.
685 686 687
class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
  /// Creates a [RenderBox] that sizes itself to the child and modifies the
  /// [constraints] before passing it down to that child.
688
  ///
689 690
  /// The [alignment] and [clipBehavior] must not be null.
  RenderConstraintsTransformBox({
691 692
    required super.alignment,
    required super.textDirection,
693
    required BoxConstraintsTransform constraintsTransform,
694
    super.child,
695
    Clip clipBehavior = Clip.none,
696
  }) : _constraintsTransform = constraintsTransform,
697
       _clipBehavior = clipBehavior;
698

699 700 701 702
  /// {@macro flutter.widgets.constraintsTransform}
  BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
  BoxConstraintsTransform _constraintsTransform;
  set constraintsTransform(BoxConstraintsTransform value) {
703
    if (_constraintsTransform == value) {
704
      return;
705
    }
706 707 708 709 710 711
    _constraintsTransform = value;
    // The RenderObject only needs layout if the new transform maps the current
    // `constraints` to a different value, or the render object has never been
    // laid out before.
    final bool needsLayout = _childConstraints == null
                          || _childConstraints != value(constraints);
712
    if (needsLayout) {
713
      markNeedsLayout();
714
    }
715
  }
716

717
  /// {@macro flutter.material.Material.clipBehavior}
718
  ///
719 720 721
  /// {@macro flutter.widgets.ConstraintsTransformBox.clipBehavior}
  ///
  /// Defaults to [Clip.none].
722
  Clip get clipBehavior => _clipBehavior;
723
  Clip _clipBehavior;
724 725 726 727 728 729 730 731
  set clipBehavior(Clip value) {
    if (value != _clipBehavior) {
      _clipBehavior = value;
      markNeedsPaint();
      markNeedsSemanticsUpdate();
    }
  }

732 733 734 735 736
  @override
  double computeMinIntrinsicHeight(double width) {
    return super.computeMinIntrinsicHeight(
      constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
    );
737 738
  }

739
  @override
740 741 742
  double computeMaxIntrinsicHeight(double width) {
    return super.computeMaxIntrinsicHeight(
      constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
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
  @override
  double computeMinIntrinsicWidth(double height) {
    return super.computeMinIntrinsicWidth(
      constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
    );
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    return super.computeMaxIntrinsicWidth(
      constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
    );
  }

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    final Size? childSize = child?.getDryLayout(constraintsTransform(constraints));
    return childSize == null ? constraints.smallest : constraints.constrain(childSize);
  }

  Rect _overflowContainerRect = Rect.zero;
  Rect _overflowChildRect = Rect.zero;
  bool _isOverflowing = false;

  BoxConstraints? _childConstraints;

772 773
  @override
  void performLayout() {
774
    final BoxConstraints constraints = this.constraints;
775
    final RenderBox? child = this.child;
776
    if (child != null) {
777 778 779 780 781
      final BoxConstraints childConstraints = constraintsTransform(constraints);
      assert(childConstraints.isNormalized, '$childConstraints is not normalized');
      _childConstraints = childConstraints;
      child.layout(childConstraints, parentUsesSize: true);
      size = constraints.constrain(child.size);
782
      alignChild();
783
      final BoxParentData childParentData = child.parentData! as BoxParentData;
784
      _overflowContainerRect = Offset.zero & size;
785
      _overflowChildRect = childParentData.offset & child.size;
786
    } else {
787
      size = constraints.smallest;
788 789 790
      _overflowContainerRect = Rect.zero;
      _overflowChildRect = Rect.zero;
    }
791
    _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
792 793 794 795 796 797
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // There's no point in drawing the child if we're empty, or there is no
    // child.
798
    if (child == null || size.isEmpty) {
799
      return;
800
    }
801 802 803 804 805 806

    if (!_isOverflowing) {
      super.paint(context, offset);
      return;
    }

807 808 809 810 811 812 813 814 815
    // We have overflow and the clipBehavior isn't none. Clip it.
    _clipRectLayer.layer = context.pushClipRect(
      needsCompositing,
      offset,
      Offset.zero & size,
      super.paint,
      clipBehavior: clipBehavior,
      oldLayer: _clipRectLayer.layer,
    );
816

817
    // Display the overflow indicator if clipBehavior is Clip.none.
818
    assert(() {
819 820 821 822 823 824 825 826
      switch (clipBehavior) {
        case Clip.none:
          paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
        case Clip.hardEdge:
        case Clip.antiAlias:
        case Clip.antiAliasWithSaveLayer:
          break;
      }
827 828 829 830
      return true;
    }());
  }

831 832 833 834 835 836 837
  final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();

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

839
  @override
840
  Rect? describeApproximatePaintClip(RenderObject child) {
841 842 843 844 845 846 847 848
    switch (clipBehavior) {
      case Clip.none:
        return null;
      case Clip.hardEdge:
      case Clip.antiAlias:
      case Clip.antiAliasWithSaveLayer:
        return _isOverflowing ? Offset.zero & size : null;
    }
849 850 851 852 853
  }

  @override
  String toStringShort() {
    String header = super.toStringShort();
Ian Hickson's avatar
Ian Hickson committed
854
    if (!kReleaseMode) {
855
      if (_isOverflowing) {
Ian Hickson's avatar
Ian Hickson committed
856
        header += ' OVERFLOWING';
857
      }
Ian Hickson's avatar
Ian Hickson committed
858
    }
859 860 861 862 863 864 865
    return header;
  }
}

/// A render object that is a specific size but passes its original constraints
/// through to its child, which it allows to overflow.
///
866 867 868
/// If the child's resulting size differs from this render object's size, then
/// the child is aligned according to the [alignment] property.
///
869
/// See also:
870
///
871 872 873
///  * [RenderConstraintsTransformBox] for a render object that applies an
///    arbitrary transform to its constraints before sizing its child using
///    the new constraints, treating any overflow as error.
874 875 876
///  * [RenderConstrainedOverflowBox] for a render object that imposes
///    different constraints on its child than it gets from its parent,
///    possibly allowing the child to overflow the parent.
877
class RenderSizedOverflowBox extends RenderAligningShiftedBox {
878 879
  /// Creates a render box of a given size that lets its child overflow.
  ///
880 881 882 883
  /// The [requestedSize] and [alignment] arguments must not be null.
  ///
  /// The [textDirection] argument must not be null if the [alignment] is
  /// direction-sensitive.
884
  RenderSizedOverflowBox({
885
    super.child,
886
    required Size requestedSize,
887 888
    super.alignment,
    super.textDirection,
889
  }) : _requestedSize = requestedSize;
890 891 892 893

  /// The size this render box should attempt to be.
  Size get requestedSize => _requestedSize;
  Size _requestedSize;
894
  set requestedSize(Size value) {
895
    if (_requestedSize == value) {
896
      return;
897
    }
898 899 900 901 902
    _requestedSize = value;
    markNeedsLayout();
  }

  @override
903
  double computeMinIntrinsicWidth(double height) {
904
    return _requestedSize.width;
905 906 907
  }

  @override
908
  double computeMaxIntrinsicWidth(double height) {
909
    return _requestedSize.width;
910 911 912
  }

  @override
913
  double computeMinIntrinsicHeight(double width) {
914
    return _requestedSize.height;
915 916 917
  }

  @override
918
  double computeMaxIntrinsicHeight(double width) {
919
    return _requestedSize.height;
920 921 922
  }

  @override
923
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
924
    if (child != null) {
925
      return child!.getDistanceToActualBaseline(baseline);
926
    }
927 928 929
    return super.computeDistanceToActualBaseline(baseline);
  }

930 931 932 933 934
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.constrain(_requestedSize);
  }

935 936 937 938
  @override
  void performLayout() {
    size = constraints.constrain(_requestedSize);
    if (child != null) {
939
      child!.layout(constraints, parentUsesSize: true);
940 941 942 943 944 945 946 947 948 949 950 951 952
      alignChild();
    }
  }
}

/// Sizes its child to a fraction of the total available space.
///
/// For both its width and height, this render object imposes a tight
/// constraint on its child that is a multiple (typically less than 1.0) of the
/// maximum constraint it received from its parent on that axis. If the factor
/// for a given axis is null, then the constraints from the parent are just
/// passed through instead.
///
953 954 955
/// It then tries to size itself to the size of its child. Where this is not
/// possible (e.g. if the constraints from the parent are themselves tight), the
/// child is aligned according to [alignment].
956
class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
957 958 959 960
  /// Creates a render box that sizes its child to a fraction of the total available space.
  ///
  /// If non-null, the [widthFactor] and [heightFactor] arguments must be
  /// non-negative.
961 962 963 964 965
  ///
  /// The [alignment] must not be null.
  ///
  /// The [textDirection] must be non-null if the [alignment] is
  /// direction-sensitive.
966
  RenderFractionallySizedOverflowBox({
967
    super.child,
968 969
    double? widthFactor,
    double? heightFactor,
970 971
    super.alignment,
    super.textDirection,
972
  }) : _widthFactor = widthFactor,
973
       _heightFactor = heightFactor {
974 975
    assert(_widthFactor == null || _widthFactor! >= 0.0);
    assert(_heightFactor == null || _heightFactor! >= 0.0);
976 977 978 979 980
  }

  /// If non-null, the factor of the incoming width to use.
  ///
  /// If non-null, the child is given a tight width constraint that is the max
981
  /// incoming width constraint multiplied by this factor. If null, the child is
982
  /// given the incoming width constraints.
983 984 985
  double? get widthFactor => _widthFactor;
  double? _widthFactor;
  set widthFactor(double? value) {
986
    assert(value == null || value >= 0.0);
987
    if (_widthFactor == value) {
988
      return;
989
    }
990 991 992 993 994 995 996
    _widthFactor = value;
    markNeedsLayout();
  }

  /// If non-null, the factor of the incoming height to use.
  ///
  /// If non-null, the child is given a tight height constraint that is the max
997
  /// incoming width constraint multiplied by this factor. If null, the child is
998
  /// given the incoming width constraints.
999 1000 1001
  double? get heightFactor => _heightFactor;
  double? _heightFactor;
  set heightFactor(double? value) {
1002
    assert(value == null || value >= 0.0);
1003
    if (_heightFactor == value) {
1004
      return;
1005
    }
1006 1007 1008 1009 1010 1011 1012 1013
    _heightFactor = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    double minWidth = constraints.minWidth;
    double maxWidth = constraints.maxWidth;
    if (_widthFactor != null) {
1014
      final double width = maxWidth * _widthFactor!;
1015 1016 1017 1018 1019 1020
      minWidth = width;
      maxWidth = width;
    }
    double minHeight = constraints.minHeight;
    double maxHeight = constraints.maxHeight;
    if (_heightFactor != null) {
1021
      final double height = maxHeight * _heightFactor!;
1022 1023 1024
      minHeight = height;
      maxHeight = height;
    }
1025
    return BoxConstraints(
1026 1027 1028
      minWidth: minWidth,
      maxWidth: maxWidth,
      minHeight: minHeight,
1029
      maxHeight: maxHeight,
1030 1031 1032 1033
    );
  }

  @override
1034
  double computeMinIntrinsicWidth(double height) {
1035
    final double result;
1036
    if (child == null) {
1037
      result = super.computeMinIntrinsicWidth(height);
1038
    } else { // the following line relies on double.infinity absorption
1039
      result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
1040 1041 1042
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
1043 1044 1045
  }

  @override
1046
  double computeMaxIntrinsicWidth(double height) {
1047
    final double result;
1048
    if (child == null) {
1049
      result = super.computeMaxIntrinsicWidth(height);
1050
    } else { // the following line relies on double.infinity absorption
1051
      result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
1052 1053 1054
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
1055 1056 1057
  }

  @override
1058
  double computeMinIntrinsicHeight(double width) {
1059
    final double result;
1060
    if (child == null) {
1061
      result = super.computeMinIntrinsicHeight(width);
1062
    } else { // the following line relies on double.infinity absorption
1063
      result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
1064 1065 1066
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
1067 1068 1069
  }

  @override
1070
  double computeMaxIntrinsicHeight(double width) {
1071
    final double result;
1072
    if (child == null) {
1073
      result = super.computeMaxIntrinsicHeight(width);
1074
    } else { // the following line relies on double.infinity absorption
1075
      result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
1076 1077 1078
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
1079 1080
  }

1081 1082 1083 1084 1085 1086 1087 1088 1089
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    if (child != null) {
      final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
      return constraints.constrain(childSize);
    }
    return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
  }

1090 1091 1092
  @override
  void performLayout() {
    if (child != null) {
1093 1094
      child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child!.size);
1095 1096 1097 1098 1099 1100 1101
      alignChild();
    } else {
      size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
    }
  }

  @override
1102 1103
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1104 1105
    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
1106 1107 1108
  }
}

Adam Barth's avatar
Adam Barth committed
1109
/// A delegate for computing the layout of a render object with a single child.
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
///
/// Used by [CustomSingleChildLayout] (in the widgets library) and
/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
///
/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
/// its incoming constraints to determine its size. It then calls
/// [getConstraintsForChild] to determine the constraints to apply to the child.
/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
/// calls [getPositionForChild] to determine the child's position.
///
/// The [shouldRelayout] method is called when a new instance of the class
/// is provided, to check if the new instance actually represents different
/// information.
///
1124
/// The most efficient way to trigger a relayout is to supply a `relayout`
1125
/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
1126 1127 1128
/// layout will listen to this value and relayout whenever the Listenable
/// notifies its listeners, such as when an [Animation] ticks. This allows
/// the custom layout to avoid the build phase of the pipeline.
1129 1130 1131 1132 1133 1134
///
/// See also:
///
///  * [CustomSingleChildLayout], the widget that uses this delegate.
///  * [RenderCustomSingleChildLayoutBox], render object that uses this
///    delegate.
1135
abstract class SingleChildLayoutDelegate {
1136 1137 1138
  /// Creates a layout delegate.
  ///
  /// The layout will update whenever [relayout] notifies its listeners.
1139
  const SingleChildLayoutDelegate({ Listenable? relayout }) : _relayout = relayout;
1140

1141
  final Listenable? _relayout;
1142 1143 1144

  /// The size of this object given the incoming constraints.
  ///
1145
  /// Defaults to the biggest size that satisfies the given constraints.
Adam Barth's avatar
Adam Barth committed
1146 1147
  Size getSize(BoxConstraints constraints) => constraints.biggest;

1148 1149 1150 1151 1152 1153 1154
  /// The constraints for the child given the incoming constraints.
  ///
  /// During layout, the child is given the layout constraints returned by this
  /// function. The child is required to pick a size for itself that satisfies
  /// these constraints.
  ///
  /// Defaults to the given constraints.
Adam Barth's avatar
Adam Barth committed
1155 1156
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;

1157 1158 1159 1160 1161 1162 1163 1164 1165
  /// The position where the child should be placed.
  ///
  /// The `size` argument is the size of the parent, which might be different
  /// from the value returned by [getSize] if that size doesn't satisfy the
  /// constraints passed to [getSize]. The `childSize` argument is the size of
  /// the child, which will satisfy the constraints returned by
  /// [getConstraintsForChild].
  ///
  /// Defaults to positioning the child in the upper left corner of the parent.
1166
  Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1167

1168 1169 1170 1171 1172 1173 1174
  /// Called whenever a new instance of the custom layout delegate class is
  /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
  /// that a new [CustomSingleChildLayout] object is created with a new instance
  /// of the custom layout delegate class (which amounts to the same thing,
  /// because the latter is implemented in terms of the former).
  ///
  /// If the new instance represents different information than the old
1175 1176
  /// instance, then the method should return true, otherwise it should return
  /// false.
1177
  ///
1178
  /// If the method returns false, then the [getSize],
1179 1180 1181 1182
  /// [getConstraintsForChild], and [getPositionForChild] calls might be
  /// optimized away.
  ///
  /// It's possible that the layout methods will get called even if
1183
  /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1184 1185 1186
  /// It's also possible that the layout method will get called
  /// without [shouldRelayout] being called at all (e.g. if the parent changes
  /// size).
1187
  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
Adam Barth's avatar
Adam Barth committed
1188 1189
}

1190 1191 1192 1193 1194 1195
/// Defers the layout of its single child to a delegate.
///
/// The delegate can determine the layout constraints for the child and can
/// decide where to position the child. The delegate can also determine the size
/// of the parent, but the size of the parent cannot depend on the size of the
/// child.
1196
class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1197
  /// Creates a render box that defers its layout to a delegate.
1198 1199
  ///
  /// The [delegate] argument must not be null.
1200
  RenderCustomSingleChildLayoutBox({
1201 1202
    RenderBox? child,
    required SingleChildLayoutDelegate delegate,
1203
  }) : _delegate = delegate,
1204
       super(child);
Adam Barth's avatar
Adam Barth committed
1205

1206
  /// A delegate that controls this object's layout.
1207 1208
  SingleChildLayoutDelegate get delegate => _delegate;
  SingleChildLayoutDelegate _delegate;
1209
  set delegate(SingleChildLayoutDelegate newDelegate) {
1210
    if (_delegate == newDelegate) {
Adam Barth's avatar
Adam Barth committed
1211
      return;
1212
    }
1213
    final SingleChildLayoutDelegate oldDelegate = _delegate;
1214
    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate)) {
1215
      markNeedsLayout();
1216
    }
Adam Barth's avatar
Adam Barth committed
1217
    _delegate = newDelegate;
1218
    if (attached) {
1219 1220
      oldDelegate._relayout?.removeListener(markNeedsLayout);
      newDelegate._relayout?.addListener(markNeedsLayout);
1221 1222 1223 1224 1225 1226
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
1227
    _delegate._relayout?.addListener(markNeedsLayout);
1228 1229 1230 1231
  }

  @override
  void detach() {
1232
    _delegate._relayout?.removeListener(markNeedsLayout);
1233
    super.detach();
Adam Barth's avatar
Adam Barth committed
1234 1235 1236 1237 1238 1239
  }

  Size _getSize(BoxConstraints constraints) {
    return constraints.constrain(_delegate.getSize(constraints));
  }

1240 1241 1242 1243
  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
  // figure out the intrinsic dimensions. We really should either not support intrinsics,
  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.

1244
  @override
1245
  double computeMinIntrinsicWidth(double height) {
1246
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1247
    if (width.isFinite) {
1248
      return width;
1249
    }
1250
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1251 1252
  }

1253
  @override
1254
  double computeMaxIntrinsicWidth(double height) {
1255
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1256
    if (width.isFinite) {
1257
      return width;
1258
    }
1259
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1260 1261
  }

1262
  @override
1263
  double computeMinIntrinsicHeight(double width) {
1264
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1265
    if (height.isFinite) {
1266
      return height;
1267
    }
1268
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1269 1270
  }

1271
  @override
1272
  double computeMaxIntrinsicHeight(double width) {
1273
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1274
    if (height.isFinite) {
1275
      return height;
1276
    }
1277
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1278 1279
  }

1280 1281 1282 1283 1284
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return _getSize(constraints);
  }

1285
  @override
Adam Barth's avatar
Adam Barth committed
1286
  void performLayout() {
1287
    size = _getSize(constraints);
Adam Barth's avatar
Adam Barth committed
1288
    if (child != null) {
1289
      final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1290
      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1291
      child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1292
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
1293
      childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size);
Adam Barth's avatar
Adam Barth committed
1294 1295 1296 1297
    }
  }
}

1298 1299 1300
/// Shifts the child down such that the child's baseline (or the
/// bottom of the child, if the child has no baseline) is [baseline]
/// logical pixels below the top of this box, then sizes this box to
1301 1302 1303 1304
/// contain the child.
///
/// If [baseline] is less than the distance from the top of the child
/// to the baseline of the child, then the child will overflow the top
1305
/// of the box. This is typically not desirable, in particular, that
1306 1307 1308 1309 1310 1311 1312 1313
/// part of the child will not be found when doing hit tests, so the
/// user cannot interact with that part of the child.
///
/// This box will be sized so that its bottom is coincident with the
/// bottom of the child. This means if this box shifts the child down,
/// there will be space between the top of this box and the top of the
/// child, but there is never space between the bottom of the child
/// and the bottom of the box.
1314
class RenderBaseline extends RenderShiftedBox {
1315 1316
  /// Creates a [RenderBaseline] object.
  ///
1317
  /// The [baseline] and [baselineType] arguments must not be null.
1318
  RenderBaseline({
1319 1320 1321
    RenderBox? child,
    required double baseline,
    required TextBaseline baselineType,
1322
  }) : _baseline = baseline,
1323
       _baselineType = baselineType,
1324
       super(child);
1325

1326 1327
  /// The number of logical pixels from the top of this box at which to position
  /// the child's baseline.
1328
  double get baseline => _baseline;
1329
  double _baseline;
1330
  set baseline(double value) {
1331
    if (_baseline == value) {
1332
      return;
1333
    }
1334 1335 1336 1337
    _baseline = value;
    markNeedsLayout();
  }

1338
  /// The type of baseline to use for positioning the child.
1339
  TextBaseline get baselineType => _baselineType;
1340
  TextBaseline _baselineType;
1341
  set baselineType(TextBaseline value) {
1342
    if (_baselineType == value) {
1343
      return;
1344
    }
1345 1346 1347 1348
    _baselineType = value;
    markNeedsLayout();
  }

1349 1350 1351 1352 1353 1354
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    if (child != null) {
      assert(debugCannotComputeDryLayout(
        reason: 'Baseline metrics are only available after a full layout.',
      ));
1355
      return Size.zero;
1356 1357 1358 1359
    }
    return constraints.smallest;
  }

1360
  @override
1361 1362
  void performLayout() {
    if (child != null) {
1363
      final BoxConstraints constraints = this.constraints;
1364 1365
      child!.layout(constraints.loosen(), parentUsesSize: true);
      final double childBaseline = child!.getDistanceToBaseline(baselineType)!;
1366
      final double actualBaseline = baseline;
1367
      final double top = actualBaseline - childBaseline;
1368
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
1369
      childParentData.offset = Offset(0.0, top);
1370
      final Size childSize = child!.size;
1371
      size = constraints.constrain(Size(childSize.width, top + childSize.height));
1372
    } else {
1373
      size = constraints.smallest;
1374 1375 1376
    }
  }

1377
  @override
1378 1379
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1380 1381
    properties.add(DoubleProperty('baseline', baseline));
    properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1382
  }
1383
}