shifted_box.dart 51.9 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
    if (child != null)
35
      return child!.getMinIntrinsicWidth(height);
36
    return 0.0;
37 38
  }

39
  @override
40
  double computeMaxIntrinsicWidth(double height) {
41
    if (child != null)
42
      return child!.getMaxIntrinsicWidth(height);
43
    return 0.0;
44 45
  }

46
  @override
47
  double computeMinIntrinsicHeight(double width) {
48
    if (child != null)
49
      return child!.getMinIntrinsicHeight(width);
50
    return 0.0;
51 52
  }

53
  @override
54
  double computeMaxIntrinsicHeight(double width) {
55
    if (child != null)
56
      return child!.getMaxIntrinsicHeight(width);
57
    return 0.0;
58 59
  }

60
  @override
61 62
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
    double? result;
63
    if (child != null) {
64
      assert(!debugNeedsLayout);
65
      result = child!.getDistanceToActualBaseline(baseline);
66
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
67
      if (result != null)
68
        result += childParentData.offset.dy;
69 70 71 72 73 74
    } else {
      result = super.computeDistanceToActualBaseline(baseline);
    }
    return result;
  }

75
  @override
76
  void paint(PaintingContext context, Offset offset) {
Hixie's avatar
Hixie committed
77
    if (child != null) {
78
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
79
      context.paintChild(child!, childParentData.offset + offset);
Hixie's avatar
Hixie committed
80
    }
81 82
  }

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

100 101 102 103 104 105
/// 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.
106
class RenderPadding extends RenderShiftedBox {
107 108 109
  /// Creates a render object that insets its child.
  ///
  /// The [padding] argument must not be null and must have non-negative insets.
110
  RenderPadding({
111 112 113
    required EdgeInsetsGeometry padding,
    TextDirection? textDirection,
    RenderBox? child,
114 115
  }) : assert(padding != null),
       assert(padding.isNonNegative),
116
       _textDirection = textDirection,
117
       _padding = padding,
Ian Hickson's avatar
Ian Hickson committed
118
       super(child);
119

120
  EdgeInsets? _resolvedPadding;
121

Ian Hickson's avatar
Ian Hickson committed
122 123 124 125
  void _resolve() {
    if (_resolvedPadding != null)
      return;
    _resolvedPadding = padding.resolve(textDirection);
126
    assert(_resolvedPadding!.isNonNegative);
Ian Hickson's avatar
Ian Hickson committed
127 128 129 130 131
  }

  void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
132
  }
133

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

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

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

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

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

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

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  @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,
    ));
  }

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

242
  @override
243 244 245
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
246
      final Rect outerRect = offset & size;
247
      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding!.deflateRect(outerRect) : null);
248
      return true;
249
    }());
250 251
  }

252
  @override
253 254
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
255 256
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
257
  }
258 259
}

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

278
  /// A constructor to be used only when the extending class also has a mixin.
279
  // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed.
280
  @protected
281
  RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection? textDirection, RenderBox? child)
282 283
    : this(alignment: alignment, textDirection: textDirection, child: child);

284
  Alignment? _resolvedAlignment;
285

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

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

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

  /// The text direction with which to resolve [alignment].
Ian Hickson's avatar
Ian Hickson committed
323 324 325
  ///
  /// This may be changed to null, but only after [alignment] has been changed
  /// to a value that does not depend on the direction.
326 327 328
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
329 330 331
    if (_textDirection == value)
      return;
    _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
    _widthFactor = value;
396 397 398
    markNeedsLayout();
  }

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

412 413 414 415 416 417 418 419
  @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,
420 421
        shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity,
      ));
422 423 424 425 426 427 428
    }
    return constraints.constrain(Size(
      shrinkWrapWidth ? 0.0 : double.infinity,
      shrinkWrapHeight ? 0.0 : double.infinity,
    ));
  }

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

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

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

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

516 517 518 519 520 521 522 523 524 525 526 527 528
/// 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
529 530 531 532
/// 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.
533
///
534
/// The child is positioned according to [alignment]. To position a smaller
535
/// child inside a larger parent, use [RenderPositionedBox] and
536
/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
537 538 539
///
/// See also:
///
540 541 542
///  * [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.
543 544 545
///  * [RenderSizedOverflowBox], a render object that is a specific size but
///    passes its original constraints through to its child, which it allows to
///    overflow.
546
class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
547
  /// Creates a render object that lets its child overflow itself.
548
  RenderConstrainedOverflowBox({
549
    super.child,
550 551 552 553
    double? minWidth,
    double? maxWidth,
    double? minHeight,
    double? maxHeight,
554 555
    super.alignment,
    super.textDirection,
556 557 558
  }) : _minWidth = minWidth,
       _maxWidth = maxWidth,
       _minHeight = minHeight,
559
       _maxHeight = maxHeight;
560 561 562

  /// The minimum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
563 564 565
  double? get minWidth => _minWidth;
  double? _minWidth;
  set minWidth(double? value) {
566 567 568 569 570 571 572 573
    if (_minWidth == value)
      return;
    _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.
574 575 576
  double? get maxWidth => _maxWidth;
  double? _maxWidth;
  set maxWidth(double? value) {
577 578 579 580 581 582 583 584
    if (_maxWidth == value)
      return;
    _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.
585 586 587
  double? get minHeight => _minHeight;
  double? _minHeight;
  set minHeight(double? value) {
588 589 590 591 592 593 594 595
    if (_minHeight == value)
      return;
    _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.
596 597 598
  double? get maxHeight => _maxHeight;
  double? _maxHeight;
  set maxHeight(double? value) {
599 600 601 602 603 604 605
    if (_maxHeight == value)
      return;
    _maxHeight = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
606
    return BoxConstraints(
607 608 609
      minWidth: _minWidth ?? constraints.minWidth,
      maxWidth: _maxWidth ?? constraints.maxWidth,
      minHeight: _minHeight ?? constraints.minHeight,
610
      maxHeight: _maxHeight ?? constraints.maxHeight,
611 612 613
    );
  }

614
  @override
615 616
  bool get sizedByParent => true;

617
  @override
618 619
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.biggest;
620 621
  }

622
  @override
623 624
  void performLayout() {
    if (child != null) {
625
      child?.layout(_getInnerConstraints(constraints), parentUsesSize: true);
626
      alignChild();
627 628 629
    }
  }

630
  @override
631 632
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
633 634 635 636
    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'));
637 638 639
  }
}

640 641 642
/// A [RenderBox] that applies an arbitrary transform to its [constraints]
/// before sizing its child using the new constraints, treating any overflow as
/// error.
643
///
644 645 646 647 648 649 650
/// 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].
651 652
///
/// In debug mode, if the child overflows the box, a warning will be printed on
653
/// the console, and black and yellow striped areas will appear where the
654 655
/// overflow occurs.
///
656 657 658
/// When [child] is null, this [RenderBox] takes the smallest possible size and
/// never overflows.
///
659
/// This [RenderBox] can be used to ensure some of [child]'s natural dimensions
660 661 662 663 664 665 666
/// 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.
///
667 668
/// See also:
///
669 670
///  * [ConstraintsTransformBox], the widget that makes use of this
///    [RenderObject] and exposes the same functionality.
671 672 673
///  * [RenderConstrainedBox], which renders a box which imposes constraints
///    on its child.
///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
674 675
///    constraints on its child than it gets from its parent, possibly allowing
///    the child to overflow the parent.
676 677 678
///  * [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.
679 680 681
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.
682
  ///
683 684
  /// The [alignment] and [clipBehavior] must not be null.
  RenderConstraintsTransformBox({
685 686
    required AlignmentGeometry alignment,
    required TextDirection? textDirection,
687
    required BoxConstraintsTransform constraintsTransform,
688
    RenderBox? child,
689
    Clip clipBehavior = Clip.none,
690
  }) : assert(alignment != null),
691
       assert(clipBehavior != null),
692 693
       assert(constraintsTransform != null),
       _constraintsTransform = constraintsTransform,
694
       _clipBehavior = clipBehavior,
695
       super.mixin(alignment, textDirection, child);
696

697 698 699 700 701
  /// {@macro flutter.widgets.constraintsTransform}
  BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
  BoxConstraintsTransform _constraintsTransform;
  set constraintsTransform(BoxConstraintsTransform value) {
    if (_constraintsTransform == value)
702
      return;
703 704 705 706 707 708 709 710
    _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);
    if (needsLayout)
      markNeedsLayout();
711
  }
712

713
  /// {@macro flutter.material.Material.clipBehavior}
714 715 716
  ///
  /// Defaults to [Clip.none], and must not be null.
  Clip get clipBehavior => _clipBehavior;
717
  Clip _clipBehavior;
718 719 720 721 722 723 724 725 726
  set clipBehavior(Clip value) {
    assert(value != null);
    if (value != _clipBehavior) {
      _clipBehavior = value;
      markNeedsPaint();
      markNeedsSemanticsUpdate();
    }
  }

727 728 729 730 731
  @override
  double computeMinIntrinsicHeight(double width) {
    return super.computeMinIntrinsicHeight(
      constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
    );
732 733
  }

734
  @override
735 736 737
  double computeMaxIntrinsicHeight(double width) {
    return super.computeMaxIntrinsicHeight(
      constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
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
  @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;

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

  @override
  void paint(PaintingContext context, Offset offset) {
    // There's no point in drawing the child if we're empty, or there is no
    // child.
    if (child == null || size.isEmpty)
      return;

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

802
    if (clipBehavior == Clip.none) {
803
      _clipRectLayer.layer = null;
804 805 806
      super.paint(context, offset);
    } else {
      // We have overflow and the clipBehavior isn't none. Clip it.
807
      _clipRectLayer.layer = context.pushClipRect(
808 809 810 811 812
        needsCompositing,
        offset,
        Offset.zero & size,
        super.paint,
        clipBehavior: clipBehavior,
813
        oldLayer: _clipRectLayer.layer,
814
      );
815
    }
816 817 818 819 820 821 822 823

    // Display the overflow indicator.
    assert(() {
      paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
      return true;
    }());
  }

824 825 826 827 828 829 830
  final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();

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

832
  @override
833
  Rect? describeApproximatePaintClip(RenderObject child) {
834 835 836 837 838 839
    return _isOverflowing ? Offset.zero & size : null;
  }

  @override
  String toStringShort() {
    String header = super.toStringShort();
Ian Hickson's avatar
Ian Hickson committed
840 841 842 843
    if (!kReleaseMode) {
      if (_isOverflowing)
        header += ' OVERFLOWING';
    }
844 845 846 847
    return header;
  }
}

848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
/// Renders a box, imposing no constraints on its child, allowing the child to
/// render at its "natural" size.
///
/// The class is deprecated, use [RenderConstraintsTransformBox] instead.
///
/// This allows a child to render at the size it would render if it were alone
/// on an infinite canvas with no 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.
///
/// In debug mode, if the child overflows the box, a warning will be printed on
/// the console, and black and yellow striped areas will appear where the
/// overflow occurs.
///
/// See also:
///
///  * [RenderConstrainedBox], which renders a box which imposes constraints
///    on its child.
///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
///    constraints on its child than it gets from its parent, possibly allowing
///    the child to overflow the parent.
///  * [RenderSizedOverflowBox], a render object that is a specific size but
///    passes its original constraints through to its child, which it allows to
///    overflow.
///
@Deprecated(
  'Use RenderConstraintsTransformBox instead. '
877
  'This feature was deprecated after v2.1.0-11.0.pre.',
878 879 880 881 882 883 884 885
)
class RenderUnconstrainedBox extends RenderConstraintsTransformBox {
  /// Create a render object that sizes itself to the child but does not
  /// pass the [constraints] down to that child.
  ///
  /// The [alignment] and [clipBehavior] must not be null.
  @Deprecated(
    'Use RenderConstraintsTransformBox instead. '
886
    'This feature was deprecated after v2.1.0-11.0.pre.',
887 888
  )
  RenderUnconstrainedBox({
889 890
    required super.alignment,
    required super.textDirection,
891
    Axis? constrainedAxis,
892 893
    super.child,
    super.clipBehavior,
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
  }) : assert(alignment != null),
       assert(clipBehavior != null),
       _constrainedAxis = constrainedAxis,
       super(
         constraintsTransform: _convertAxis(constrainedAxis),
       );

  /// The axis to retain constraints on, if any.
  ///
  /// If not set, or set to null (the default), neither axis will retain its
  /// constraints. If set to [Axis.vertical], then vertical constraints will
  /// be retained, and if set to [Axis.horizontal], then horizontal constraints
  /// will be retained.
  Axis? get constrainedAxis => _constrainedAxis;
  Axis? _constrainedAxis;
  set constrainedAxis(Axis? value) {
    if (_constrainedAxis == value)
      return;
    _constrainedAxis = value;
    constraintsTransform = _convertAxis(constrainedAxis);
  }

  static BoxConstraints _unconstrained(BoxConstraints constraints) => const BoxConstraints();
  static BoxConstraints _widthConstrained(BoxConstraints constraints) => constraints.widthConstraints();
  static BoxConstraints _heightConstrained(BoxConstraints constraints) => constraints.heightConstraints();
  static BoxConstraintsTransform _convertAxis(Axis? constrainedAxis) {
    if (constrainedAxis == null) {
      return _unconstrained;
    }
    switch (constrainedAxis) {
      case Axis.horizontal:
        return _widthConstrained;
      case Axis.vertical:
        return _heightConstrained;
    }
  }
}

932 933 934
/// A render object that is a specific size but passes its original constraints
/// through to its child, which it allows to overflow.
///
935 936 937
/// If the child's resulting size differs from this render object's size, then
/// the child is aligned according to the [alignment] property.
///
938
/// See also:
939
///
940 941 942
///  * [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.
943 944 945
///  * [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.
946
class RenderSizedOverflowBox extends RenderAligningShiftedBox {
947 948
  /// Creates a render box of a given size that lets its child overflow.
  ///
949 950 951 952
  /// The [requestedSize] and [alignment] arguments must not be null.
  ///
  /// The [textDirection] argument must not be null if the [alignment] is
  /// direction-sensitive.
953
  RenderSizedOverflowBox({
954
    super.child,
955
    required Size requestedSize,
956 957
    super.alignment,
    super.textDirection,
958
  }) : assert(requestedSize != null),
959
       _requestedSize = requestedSize;
960 961 962 963

  /// The size this render box should attempt to be.
  Size get requestedSize => _requestedSize;
  Size _requestedSize;
964
  set requestedSize(Size value) {
965 966 967 968 969 970 971 972
    assert(value != null);
    if (_requestedSize == value)
      return;
    _requestedSize = value;
    markNeedsLayout();
  }

  @override
973
  double computeMinIntrinsicWidth(double height) {
974
    return _requestedSize.width;
975 976 977
  }

  @override
978
  double computeMaxIntrinsicWidth(double height) {
979
    return _requestedSize.width;
980 981 982
  }

  @override
983
  double computeMinIntrinsicHeight(double width) {
984
    return _requestedSize.height;
985 986 987
  }

  @override
988
  double computeMaxIntrinsicHeight(double width) {
989
    return _requestedSize.height;
990 991 992
  }

  @override
993
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
994
    if (child != null)
995
      return child!.getDistanceToActualBaseline(baseline);
996 997 998
    return super.computeDistanceToActualBaseline(baseline);
  }

999 1000 1001 1002 1003
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.constrain(_requestedSize);
  }

1004 1005 1006 1007
  @override
  void performLayout() {
    size = constraints.constrain(_requestedSize);
    if (child != null) {
1008
      child!.layout(constraints, parentUsesSize: true);
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
      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.
///
1022 1023 1024
/// 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].
1025
class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
1026 1027 1028 1029
  /// 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.
1030 1031 1032 1033 1034
  ///
  /// The [alignment] must not be null.
  ///
  /// The [textDirection] must be non-null if the [alignment] is
  /// direction-sensitive.
1035
  RenderFractionallySizedOverflowBox({
1036
    super.child,
1037 1038
    double? widthFactor,
    double? heightFactor,
1039 1040
    super.alignment,
    super.textDirection,
1041
  }) : _widthFactor = widthFactor,
1042
       _heightFactor = heightFactor {
1043 1044
    assert(_widthFactor == null || _widthFactor! >= 0.0);
    assert(_heightFactor == null || _heightFactor! >= 0.0);
1045 1046 1047 1048 1049
  }

  /// 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
1050
  /// incoming width constraint multiplied by this factor. If null, the child is
1051
  /// given the incoming width constraints.
1052 1053 1054
  double? get widthFactor => _widthFactor;
  double? _widthFactor;
  set widthFactor(double? value) {
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
    assert(value == null || value >= 0.0);
    if (_widthFactor == value)
      return;
    _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
1065
  /// incoming width constraint multiplied by this factor. If null, the child is
1066
  /// given the incoming width constraints.
1067 1068 1069
  double? get heightFactor => _heightFactor;
  double? _heightFactor;
  set heightFactor(double? value) {
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
    assert(value == null || value >= 0.0);
    if (_heightFactor == value)
      return;
    _heightFactor = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    double minWidth = constraints.minWidth;
    double maxWidth = constraints.maxWidth;
    if (_widthFactor != null) {
1081
      final double width = maxWidth * _widthFactor!;
1082 1083 1084 1085 1086 1087
      minWidth = width;
      maxWidth = width;
    }
    double minHeight = constraints.minHeight;
    double maxHeight = constraints.maxHeight;
    if (_heightFactor != null) {
1088
      final double height = maxHeight * _heightFactor!;
1089 1090 1091
      minHeight = height;
      maxHeight = height;
    }
1092
    return BoxConstraints(
1093 1094 1095
      minWidth: minWidth,
      maxWidth: maxWidth,
      minHeight: minHeight,
1096
      maxHeight: maxHeight,
1097 1098 1099 1100
    );
  }

  @override
1101
  double computeMinIntrinsicWidth(double height) {
1102
    final double result;
1103
    if (child == null) {
1104
      result = super.computeMinIntrinsicWidth(height);
1105
    } else { // the following line relies on double.infinity absorption
1106
      result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
1107 1108 1109
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
1110 1111 1112
  }

  @override
1113
  double computeMaxIntrinsicWidth(double height) {
1114
    final double result;
1115
    if (child == null) {
1116
      result = super.computeMaxIntrinsicWidth(height);
1117
    } else { // the following line relies on double.infinity absorption
1118
      result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
1119 1120 1121
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
1122 1123 1124
  }

  @override
1125
  double computeMinIntrinsicHeight(double width) {
1126
    final double result;
1127
    if (child == null) {
1128
      result = super.computeMinIntrinsicHeight(width);
1129
    } else { // the following line relies on double.infinity absorption
1130
      result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
1131 1132 1133
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
1134 1135 1136
  }

  @override
1137
  double computeMaxIntrinsicHeight(double width) {
1138
    final double result;
1139
    if (child == null) {
1140
      result = super.computeMaxIntrinsicHeight(width);
1141
    } else { // the following line relies on double.infinity absorption
1142
      result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
1143 1144 1145
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
1146 1147
  }

1148 1149 1150 1151 1152 1153 1154 1155 1156
  @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));
  }

1157 1158 1159
  @override
  void performLayout() {
    if (child != null) {
1160 1161
      child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child!.size);
1162 1163 1164 1165 1166 1167 1168
      alignChild();
    } else {
      size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
    }
  }

  @override
1169 1170
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1171 1172
    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
1173 1174 1175
  }
}

Adam Barth's avatar
Adam Barth committed
1176
/// A delegate for computing the layout of a render object with a single child.
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
///
/// 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.
///
1191
/// The most efficient way to trigger a relayout is to supply a `relayout`
1192
/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
1193 1194 1195
/// 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.
1196 1197 1198 1199 1200 1201
///
/// See also:
///
///  * [CustomSingleChildLayout], the widget that uses this delegate.
///  * [RenderCustomSingleChildLayoutBox], render object that uses this
///    delegate.
1202
abstract class SingleChildLayoutDelegate {
1203 1204 1205
  /// Creates a layout delegate.
  ///
  /// The layout will update whenever [relayout] notifies its listeners.
1206
  const SingleChildLayoutDelegate({ Listenable? relayout }) : _relayout = relayout;
1207

1208
  final Listenable? _relayout;
1209 1210 1211

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

1215 1216 1217 1218 1219 1220 1221
  /// 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
1222 1223
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;

1224 1225 1226 1227 1228 1229 1230 1231 1232
  /// 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.
1233
  Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1234

1235 1236 1237 1238 1239 1240 1241
  /// 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
1242 1243
  /// instance, then the method should return true, otherwise it should return
  /// false.
1244
  ///
1245
  /// If the method returns false, then the [getSize],
1246 1247 1248 1249
  /// [getConstraintsForChild], and [getPositionForChild] calls might be
  /// optimized away.
  ///
  /// It's possible that the layout methods will get called even if
1250
  /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1251 1252 1253
  /// It's also possible that the layout method will get called
  /// without [shouldRelayout] being called at all (e.g. if the parent changes
  /// size).
1254
  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
Adam Barth's avatar
Adam Barth committed
1255 1256
}

1257 1258 1259 1260 1261 1262
/// 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.
1263
class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1264
  /// Creates a render box that defers its layout to a delegate.
1265 1266
  ///
  /// The [delegate] argument must not be null.
1267
  RenderCustomSingleChildLayoutBox({
1268 1269
    RenderBox? child,
    required SingleChildLayoutDelegate delegate,
1270 1271 1272
  }) : assert(delegate != null),
       _delegate = delegate,
       super(child);
Adam Barth's avatar
Adam Barth committed
1273

1274
  /// A delegate that controls this object's layout.
1275 1276
  SingleChildLayoutDelegate get delegate => _delegate;
  SingleChildLayoutDelegate _delegate;
1277
  set delegate(SingleChildLayoutDelegate newDelegate) {
Adam Barth's avatar
Adam Barth committed
1278 1279 1280
    assert(newDelegate != null);
    if (_delegate == newDelegate)
      return;
1281 1282
    final SingleChildLayoutDelegate oldDelegate = _delegate;
    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
1283
      markNeedsLayout();
Adam Barth's avatar
Adam Barth committed
1284
    _delegate = newDelegate;
1285
    if (attached) {
1286 1287
      oldDelegate._relayout?.removeListener(markNeedsLayout);
      newDelegate._relayout?.addListener(markNeedsLayout);
1288 1289 1290 1291 1292 1293
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
1294
    _delegate._relayout?.addListener(markNeedsLayout);
1295 1296 1297 1298
  }

  @override
  void detach() {
1299
    _delegate._relayout?.removeListener(markNeedsLayout);
1300
    super.detach();
Adam Barth's avatar
Adam Barth committed
1301 1302 1303 1304 1305 1306
  }

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

1307 1308 1309 1310
  // 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.

1311
  @override
1312
  double computeMinIntrinsicWidth(double height) {
1313
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1314 1315 1316
    if (width.isFinite)
      return width;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1317 1318
  }

1319
  @override
1320
  double computeMaxIntrinsicWidth(double height) {
1321
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1322 1323 1324
    if (width.isFinite)
      return width;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1325 1326
  }

1327
  @override
1328
  double computeMinIntrinsicHeight(double width) {
1329
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1330 1331 1332
    if (height.isFinite)
      return height;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1333 1334
  }

1335
  @override
1336
  double computeMaxIntrinsicHeight(double width) {
1337
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1338 1339 1340
    if (height.isFinite)
      return height;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1341 1342
  }

1343 1344 1345 1346 1347
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return _getSize(constraints);
  }

1348
  @override
Adam Barth's avatar
Adam Barth committed
1349
  void performLayout() {
1350
    size = _getSize(constraints);
Adam Barth's avatar
Adam Barth committed
1351
    if (child != null) {
1352
      final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1353
      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1354
      child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1355
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
1356
      childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size);
Adam Barth's avatar
Adam Barth committed
1357 1358 1359 1360
    }
  }
}

1361 1362 1363
/// 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
1364 1365 1366 1367
/// 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
1368
/// of the box. This is typically not desirable, in particular, that
1369 1370 1371 1372 1373 1374 1375 1376
/// 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.
1377
class RenderBaseline extends RenderShiftedBox {
1378 1379
  /// Creates a [RenderBaseline] object.
  ///
1380
  /// The [baseline] and [baselineType] arguments must not be null.
1381
  RenderBaseline({
1382 1383 1384
    RenderBox? child,
    required double baseline,
    required TextBaseline baselineType,
1385 1386 1387
  }) : assert(baseline != null),
       assert(baselineType != null),
       _baseline = baseline,
1388
       _baselineType = baselineType,
1389
       super(child);
1390

1391 1392
  /// The number of logical pixels from the top of this box at which to position
  /// the child's baseline.
1393
  double get baseline => _baseline;
1394
  double _baseline;
1395
  set baseline(double value) {
1396 1397 1398 1399 1400 1401 1402
    assert(value != null);
    if (_baseline == value)
      return;
    _baseline = value;
    markNeedsLayout();
  }

1403
  /// The type of baseline to use for positioning the child.
1404
  TextBaseline get baselineType => _baselineType;
1405
  TextBaseline _baselineType;
1406
  set baselineType(TextBaseline value) {
1407 1408 1409 1410 1411 1412 1413
    assert(value != null);
    if (_baselineType == value)
      return;
    _baselineType = value;
    markNeedsLayout();
  }

1414 1415 1416 1417 1418 1419
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    if (child != null) {
      assert(debugCannotComputeDryLayout(
        reason: 'Baseline metrics are only available after a full layout.',
      ));
1420
      return Size.zero;
1421 1422 1423 1424
    }
    return constraints.smallest;
  }

1425
  @override
1426 1427
  void performLayout() {
    if (child != null) {
1428
      final BoxConstraints constraints = this.constraints;
1429 1430
      child!.layout(constraints.loosen(), parentUsesSize: true);
      final double childBaseline = child!.getDistanceToBaseline(baselineType)!;
1431
      final double actualBaseline = baseline;
1432
      final double top = actualBaseline - childBaseline;
1433
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
1434
      childParentData.offset = Offset(0.0, top);
1435
      final Size childSize = child!.size;
1436
      size = constraints.constrain(Size(childSize.width, top + childSize.height));
1437
    } else {
1438
      size = constraints.smallest;
1439 1440 1441
    }
  }

1442
  @override
1443 1444
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1445 1446
    properties.add(DoubleProperty('baseline', baseline));
    properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1447
  }
1448
}