1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/widgets.dart';
6///
7/// @docImport 'proxy_box.dart';
8library;
9
10import 'dart:math' as math;
11
12import 'package:flutter/foundation.dart';
13
14import 'box.dart';
15import 'debug.dart';
16import 'debug_overflow_indicator.dart';
17import 'layer.dart';
18import 'layout_helper.dart';
19import 'object.dart';
20import 'stack.dart' show RelativeRect;
21
22/// Signature for a function that transforms a [BoxConstraints] to another
23/// [BoxConstraints].
24///
25/// Used by [RenderConstraintsTransformBox] and [ConstraintsTransformBox].
26/// Typically the caller requires the returned [BoxConstraints] to be
27/// [BoxConstraints.isNormalized].
28typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints constraints);
29
30/// Abstract class for one-child-layout render boxes that provide control over
31/// the child's position.
32abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
33 /// Initializes the [child] property for subclasses.
34 RenderShiftedBox(RenderBox? child) {
35 this.child = child;
36 }
37
38 @override
39 double computeMinIntrinsicWidth(double height) {
40 return child?.getMinIntrinsicWidth(height) ?? 0.0;
41 }
42
43 @override
44 double computeMaxIntrinsicWidth(double height) {
45 return child?.getMaxIntrinsicWidth(height) ?? 0.0;
46 }
47
48 @override
49 double computeMinIntrinsicHeight(double width) {
50 return child?.getMinIntrinsicHeight(width) ?? 0.0;
51 }
52
53 @override
54 double computeMaxIntrinsicHeight(double width) {
55 return child?.getMaxIntrinsicHeight(width) ?? 0.0;
56 }
57
58 @override
59 double? computeDistanceToActualBaseline(TextBaseline baseline) {
60 double? result;
61 final RenderBox? child = this.child;
62 assert(!debugNeedsLayout);
63 if (child != null) {
64 assert(!child.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 } else {
71 result = super.computeDistanceToActualBaseline(baseline);
72 }
73 return result;
74 }
75
76 @override
77 void paint(PaintingContext context, Offset offset) {
78 final RenderBox? child = this.child;
79 if (child != null) {
80 final BoxParentData childParentData = child.parentData! as BoxParentData;
81 context.paintChild(child, childParentData.offset + offset);
82 }
83 }
84
85 @override
86 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
87 final RenderBox? child = this.child;
88 if (child != null) {
89 final BoxParentData childParentData = child.parentData! as BoxParentData;
90 return result.addWithPaintOffset(
91 offset: childParentData.offset,
92 position: position,
93 hitTest: (BoxHitTestResult result, Offset transformed) {
94 assert(transformed == position - childParentData.offset);
95 return child.hitTest(result, position: transformed);
96 },
97 );
98 }
99 return false;
100 }
101}
102
103/// Insets its child by the given padding.
104///
105/// When passing layout constraints to its child, padding shrinks the
106/// constraints by the given padding, causing the child to layout at a smaller
107/// size. Padding then sizes itself to its child's size, inflated by the
108/// padding, effectively creating empty space around the child.
109class RenderPadding extends RenderShiftedBox {
110 /// Creates a render object that insets its child.
111 ///
112 /// The [padding] argument must have non-negative insets.
113 RenderPadding({
114 required EdgeInsetsGeometry padding,
115 TextDirection? textDirection,
116 RenderBox? child,
117 }) : assert(padding.isNonNegative),
118 _textDirection = textDirection,
119 _padding = padding,
120 super(child);
121
122 EdgeInsets? _resolvedPaddingCache;
123 EdgeInsets get _resolvedPadding {
124 final EdgeInsets returnValue = _resolvedPaddingCache ??= padding.resolve(textDirection);
125 assert(returnValue.isNonNegative);
126 return returnValue;
127 }
128
129 void _markNeedResolution() {
130 _resolvedPaddingCache = null;
131 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 EdgeInsetsGeometry get padding => _padding;
139 EdgeInsetsGeometry _padding;
140 set padding(EdgeInsetsGeometry value) {
141 assert(value.isNonNegative);
142 if (_padding == value) {
143 return;
144 }
145 _padding = value;
146 _markNeedResolution();
147 }
148
149 /// The text direction with which to resolve [padding].
150 ///
151 /// This may be changed to null, but only after the [padding] has been changed
152 /// to a value that does not depend on the direction.
153 TextDirection? get textDirection => _textDirection;
154 TextDirection? _textDirection;
155 set textDirection(TextDirection? value) {
156 if (_textDirection == value) {
157 return;
158 }
159 _textDirection = value;
160 _markNeedResolution();
161 }
162
163 @override
164 double computeMinIntrinsicWidth(double height) {
165 final EdgeInsets padding = _resolvedPadding;
166 if (child != null) {
167 // Relies on double.infinity absorption.
168 return child!.getMinIntrinsicWidth(math.max(0.0, height - padding.vertical)) +
169 padding.horizontal;
170 }
171 return padding.horizontal;
172 }
173
174 @override
175 double computeMaxIntrinsicWidth(double height) {
176 final EdgeInsets padding = _resolvedPadding;
177 if (child != null) {
178 // Relies on double.infinity absorption.
179 return child!.getMaxIntrinsicWidth(math.max(0.0, height - padding.vertical)) +
180 padding.horizontal;
181 }
182 return padding.horizontal;
183 }
184
185 @override
186 double computeMinIntrinsicHeight(double width) {
187 final EdgeInsets padding = _resolvedPadding;
188 if (child != null) {
189 // Relies on double.infinity absorption.
190 return child!.getMinIntrinsicHeight(math.max(0.0, width - padding.horizontal)) +
191 padding.vertical;
192 }
193 return padding.vertical;
194 }
195
196 @override
197 double computeMaxIntrinsicHeight(double width) {
198 final EdgeInsets padding = _resolvedPadding;
199 if (child != null) {
200 // Relies on double.infinity absorption.
201 return child!.getMaxIntrinsicHeight(math.max(0.0, width - padding.horizontal)) +
202 padding.vertical;
203 }
204 return padding.vertical;
205 }
206
207 @override
208 @protected
209 Size computeDryLayout(covariant BoxConstraints constraints) {
210 final EdgeInsets padding = _resolvedPadding;
211 if (child == null) {
212 return constraints.constrain(Size(padding.horizontal, padding.vertical));
213 }
214 final BoxConstraints innerConstraints = constraints.deflate(padding);
215 final Size childSize = child!.getDryLayout(innerConstraints);
216 return constraints.constrain(
217 Size(padding.horizontal + childSize.width, padding.vertical + childSize.height),
218 );
219 }
220
221 @override
222 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
223 final RenderBox? child = this.child;
224 if (child == null) {
225 return null;
226 }
227 final EdgeInsets padding = _resolvedPadding;
228 final BoxConstraints innerConstraints = constraints.deflate(padding);
229 final BaselineOffset result =
230 BaselineOffset(child.getDryBaseline(innerConstraints, baseline)) + padding.top;
231 return result.offset;
232 }
233
234 @override
235 void performLayout() {
236 final BoxConstraints constraints = this.constraints;
237 final EdgeInsets padding = _resolvedPadding;
238 if (child == null) {
239 size = constraints.constrain(Size(padding.horizontal, padding.vertical));
240 return;
241 }
242 final BoxConstraints innerConstraints = constraints.deflate(padding);
243 child!.layout(innerConstraints, parentUsesSize: true);
244 final BoxParentData childParentData = child!.parentData! as BoxParentData;
245 childParentData.offset = Offset(padding.left, padding.top);
246 size = constraints.constrain(
247 Size(padding.horizontal + child!.size.width, padding.vertical + child!.size.height),
248 );
249 }
250
251 @override
252 void debugPaintSize(PaintingContext context, Offset offset) {
253 super.debugPaintSize(context, offset);
254 assert(() {
255 final Rect outerRect = offset & size;
256 debugPaintPadding(
257 context.canvas,
258 outerRect,
259 child != null ? _resolvedPaddingCache!.deflateRect(outerRect) : null,
260 );
261 return true;
262 }());
263 }
264
265 @override
266 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
267 super.debugFillProperties(properties);
268 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
269 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
270 }
271}
272
273/// Abstract class for one-child-layout render boxes that use a
274/// [AlignmentGeometry] to align their children.
275abstract class RenderAligningShiftedBox extends RenderShiftedBox {
276 /// Initializes member variables for subclasses.
277 ///
278 /// The [textDirection] must be non-null if the [alignment] is
279 /// direction-sensitive.
280 RenderAligningShiftedBox({
281 AlignmentGeometry alignment = Alignment.center,
282 required TextDirection? textDirection,
283 RenderBox? child,
284 }) : _alignment = alignment,
285 _textDirection = textDirection,
286 super(child);
287
288 /// The [Alignment] to use for aligning the child.
289 ///
290 /// This is the [alignment] resolved against [textDirection]. Subclasses should
291 /// use [resolvedAlignment] instead of [alignment] directly, for computing the
292 /// child's offset.
293 ///
294 /// The [performLayout] method will be called when the value changes.
295 @protected
296 Alignment get resolvedAlignment => _resolvedAlignment ??= alignment.resolve(textDirection);
297 Alignment? _resolvedAlignment;
298
299 void _markNeedResolution() {
300 _resolvedAlignment = null;
301 markNeedsLayout();
302 }
303
304 /// How to align the child.
305 ///
306 /// The x and y values of the alignment control the horizontal and vertical
307 /// alignment, respectively. An x value of -1.0 means that the left edge of
308 /// the child is aligned with the left edge of the parent whereas an x value
309 /// of 1.0 means that the right edge of the child is aligned with the right
310 /// edge of the parent. Other values interpolate (and extrapolate) linearly.
311 /// For example, a value of 0.0 means that the center of the child is aligned
312 /// with the center of the parent.
313 ///
314 /// If this is set to an [AlignmentDirectional] object, then
315 /// [textDirection] must not be null.
316 AlignmentGeometry get alignment => _alignment;
317 AlignmentGeometry _alignment;
318
319 /// Sets the alignment to a new value, and triggers a layout update.
320 set alignment(AlignmentGeometry value) {
321 if (_alignment == value) {
322 return;
323 }
324 _alignment = value;
325 _markNeedResolution();
326 }
327
328 /// The text direction with which to resolve [alignment].
329 ///
330 /// This may be changed to null, but only after [alignment] has been changed
331 /// to a value that does not depend on the direction.
332 TextDirection? get textDirection => _textDirection;
333 TextDirection? _textDirection;
334 set textDirection(TextDirection? value) {
335 if (_textDirection == value) {
336 return;
337 }
338 _textDirection = value;
339 _markNeedResolution();
340 }
341
342 /// Apply the current [alignment] to the [child].
343 ///
344 /// Subclasses should call this method if they have a child, to have
345 /// this class perform the actual alignment. If there is no child,
346 /// do not call this method.
347 ///
348 /// This method must be called after the child has been laid out and
349 /// this object's own size has been set.
350 @protected
351 void alignChild() {
352 assert(child != null);
353 assert(!child!.debugNeedsLayout);
354 assert(child!.hasSize);
355 assert(hasSize);
356 final BoxParentData childParentData = child!.parentData! as BoxParentData;
357 childParentData.offset = resolvedAlignment.alongOffset(size - child!.size as Offset);
358 }
359
360 @override
361 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
362 super.debugFillProperties(properties);
363 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
364 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
365 }
366}
367
368/// Positions its child using an [AlignmentGeometry].
369///
370/// For example, to align a box at the bottom right, you would pass this box a
371/// tight constraint that is bigger than the child's natural size,
372/// with an alignment of [Alignment.bottomRight].
373///
374/// By default, sizes to be as big as possible in both axes. If either axis is
375/// unconstrained, then in that direction it will be sized to fit the child's
376/// dimensions. Using widthFactor and heightFactor you can force this latter
377/// behavior in all cases.
378class RenderPositionedBox extends RenderAligningShiftedBox {
379 /// Creates a render object that positions its child.
380 RenderPositionedBox({
381 super.child,
382 double? widthFactor,
383 double? heightFactor,
384 super.alignment,
385 super.textDirection,
386 }) : assert(widthFactor == null || widthFactor >= 0.0),
387 assert(heightFactor == null || heightFactor >= 0.0),
388 _widthFactor = widthFactor,
389 _heightFactor = heightFactor;
390
391 /// If non-null, sets its width to the child's width multiplied by this factor.
392 ///
393 /// Can be both greater and less than 1.0 but must be positive.
394 double? get widthFactor => _widthFactor;
395 double? _widthFactor;
396 set widthFactor(double? value) {
397 assert(value == null || value >= 0.0);
398 if (_widthFactor == value) {
399 return;
400 }
401 _widthFactor = value;
402 markNeedsLayout();
403 }
404
405 /// If non-null, sets its height to the child's height multiplied by this factor.
406 ///
407 /// Can be both greater and less than 1.0 but must be positive.
408 double? get heightFactor => _heightFactor;
409 double? _heightFactor;
410 set heightFactor(double? value) {
411 assert(value == null || value >= 0.0);
412 if (_heightFactor == value) {
413 return;
414 }
415 _heightFactor = value;
416 markNeedsLayout();
417 }
418
419 @override
420 double computeMinIntrinsicWidth(double height) {
421 return super.computeMinIntrinsicWidth(height) * (_widthFactor ?? 1);
422 }
423
424 @override
425 double computeMaxIntrinsicWidth(double height) {
426 return super.computeMaxIntrinsicWidth(height) * (_widthFactor ?? 1);
427 }
428
429 @override
430 double computeMinIntrinsicHeight(double width) {
431 return super.computeMinIntrinsicHeight(width) * (_heightFactor ?? 1);
432 }
433
434 @override
435 double computeMaxIntrinsicHeight(double width) {
436 return super.computeMaxIntrinsicHeight(width) * (_heightFactor ?? 1);
437 }
438
439 @override
440 @protected
441 Size computeDryLayout(covariant BoxConstraints constraints) {
442 final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
443 final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
444 if (child != null) {
445 final Size childSize = child!.getDryLayout(constraints.loosen());
446 return constraints.constrain(
447 Size(
448 shrinkWrapWidth ? childSize.width * (_widthFactor ?? 1.0) : double.infinity,
449 shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity,
450 ),
451 );
452 }
453 return constraints.constrain(
454 Size(shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity),
455 );
456 }
457
458 @override
459 void performLayout() {
460 final BoxConstraints constraints = this.constraints;
461 final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
462 final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
463
464 if (child != null) {
465 child!.layout(constraints.loosen(), parentUsesSize: true);
466 size = constraints.constrain(
467 Size(
468 shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
469 shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
470 ),
471 );
472 alignChild();
473 } else {
474 size = constraints.constrain(
475 Size(shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity),
476 );
477 }
478 }
479
480 @override
481 void debugPaintSize(PaintingContext context, Offset offset) {
482 super.debugPaintSize(context, offset);
483 assert(() {
484 final Paint paint;
485 if (child != null && !child!.size.isEmpty) {
486 final Path path;
487 paint = Paint()
488 ..style = PaintingStyle.stroke
489 ..strokeWidth = 1.0
490 ..color = const Color(0xFFFFFF00);
491 path = Path();
492 final BoxParentData childParentData = child!.parentData! as BoxParentData;
493 if (childParentData.offset.dy > 0.0) {
494 // vertical alignment arrows
495 final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
496 path
497 ..moveTo(offset.dx + size.width / 2.0, offset.dy)
498 ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
499 ..relativeLineTo(headSize, 0.0)
500 ..relativeLineTo(-headSize, headSize)
501 ..relativeLineTo(-headSize, -headSize)
502 ..relativeLineTo(headSize, 0.0)
503 ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
504 ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
505 ..relativeLineTo(headSize, 0.0)
506 ..relativeLineTo(-headSize, -headSize)
507 ..relativeLineTo(-headSize, headSize)
508 ..relativeLineTo(headSize, 0.0);
509 context.canvas.drawPath(path, paint);
510 }
511 if (childParentData.offset.dx > 0.0) {
512 // horizontal alignment arrows
513 final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
514 path
515 ..moveTo(offset.dx, offset.dy + size.height / 2.0)
516 ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
517 ..relativeLineTo(0.0, headSize)
518 ..relativeLineTo(headSize, -headSize)
519 ..relativeLineTo(-headSize, -headSize)
520 ..relativeLineTo(0.0, headSize)
521 ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
522 ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
523 ..relativeLineTo(0.0, headSize)
524 ..relativeLineTo(-headSize, -headSize)
525 ..relativeLineTo(headSize, -headSize)
526 ..relativeLineTo(0.0, headSize);
527 context.canvas.drawPath(path, paint);
528 }
529 } else {
530 paint = Paint()..color = const Color(0x90909090);
531 context.canvas.drawRect(offset & size, paint);
532 }
533 return true;
534 }());
535 }
536
537 @override
538 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
539 super.debugFillProperties(properties);
540 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
541 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
542 }
543}
544
545/// How much space should be occupied by the [OverflowBox] if there is no
546/// overflow.
547enum OverflowBoxFit {
548 /// The widget will size itself to be as large as the parent allows.
549 max,
550
551 /// The widget will follow the child's size.
552 ///
553 /// More specifically, the render object will size itself to match the size of
554 /// its child within the constraints of its parent, or as small as the
555 /// parent allows if no child is set.
556 deferToChild,
557}
558
559/// A render object that imposes different constraints on its child than it gets
560/// from its parent, possibly allowing the child to overflow the parent.
561///
562/// A render overflow box proxies most functions in the render box protocol to
563/// its child, except that when laying out its child, it passes constraints
564/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
565/// just passing the parent's constraints in. Specifically, it overrides any of
566/// the equivalent fields on the constraints given by the parent with the
567/// constraints given by these fields for each such field that is not null. It
568/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
569/// ignoring the child's dimensions.
570///
571/// For example, if you wanted a box to always render 50 pixels high, regardless
572/// of where it was rendered, you would wrap it in a
573/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
574/// Generally speaking, to avoid confusing behavior around hit testing, a
575/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
576///
577/// The child is positioned according to [alignment]. To position a smaller
578/// child inside a larger parent, use [RenderPositionedBox] and
579/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
580///
581/// See also:
582///
583/// * [RenderConstraintsTransformBox] for a render object that applies an
584/// arbitrary transform to its constraints before sizing its child using
585/// the new constraints, treating any overflow as error.
586/// * [RenderSizedOverflowBox], a render object that is a specific size but
587/// passes its original constraints through to its child, which it allows to
588/// overflow.
589class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
590 /// Creates a render object that lets its child overflow itself.
591 RenderConstrainedOverflowBox({
592 super.child,
593 double? minWidth,
594 double? maxWidth,
595 double? minHeight,
596 double? maxHeight,
597 OverflowBoxFit fit = OverflowBoxFit.max,
598 super.alignment,
599 super.textDirection,
600 }) : _minWidth = minWidth,
601 _maxWidth = maxWidth,
602 _minHeight = minHeight,
603 _maxHeight = maxHeight,
604 _fit = fit;
605
606 /// The minimum width constraint to give the child. Set this to null (the
607 /// default) to use the constraint from the parent instead.
608 double? get minWidth => _minWidth;
609 double? _minWidth;
610 set minWidth(double? value) {
611 if (_minWidth == value) {
612 return;
613 }
614 _minWidth = value;
615 markNeedsLayout();
616 }
617
618 /// The maximum width constraint to give the child. Set this to null (the
619 /// default) to use the constraint from the parent instead.
620 double? get maxWidth => _maxWidth;
621 double? _maxWidth;
622 set maxWidth(double? value) {
623 if (_maxWidth == value) {
624 return;
625 }
626 _maxWidth = value;
627 markNeedsLayout();
628 }
629
630 /// The minimum height constraint to give the child. Set this to null (the
631 /// default) to use the constraint from the parent instead.
632 double? get minHeight => _minHeight;
633 double? _minHeight;
634 set minHeight(double? value) {
635 if (_minHeight == value) {
636 return;
637 }
638 _minHeight = value;
639 markNeedsLayout();
640 }
641
642 /// The maximum height constraint to give the child. Set this to null (the
643 /// default) to use the constraint from the parent instead.
644 double? get maxHeight => _maxHeight;
645 double? _maxHeight;
646 set maxHeight(double? value) {
647 if (_maxHeight == value) {
648 return;
649 }
650 _maxHeight = value;
651 markNeedsLayout();
652 }
653
654 /// The way to size the render object.
655 ///
656 /// This only affects scenario when the child does not indeed overflow.
657 /// If set to [OverflowBoxFit.deferToChild], the render object will size
658 /// itself to match the size of its child within the constraints of its
659 /// parent, or as small as the parent allows if no child is set.
660 /// If set to [OverflowBoxFit.max] (the default), the
661 /// render object will size itself to be as large as the parent allows.
662 OverflowBoxFit get fit => _fit;
663 OverflowBoxFit _fit;
664 set fit(OverflowBoxFit value) {
665 if (_fit == value) {
666 return;
667 }
668 _fit = value;
669 markNeedsLayoutForSizedByParentChange();
670 }
671
672 BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
673 return BoxConstraints(
674 minWidth: _minWidth ?? constraints.minWidth,
675 maxWidth: _maxWidth ?? constraints.maxWidth,
676 minHeight: _minHeight ?? constraints.minHeight,
677 maxHeight: _maxHeight ?? constraints.maxHeight,
678 );
679 }
680
681 @override
682 bool get sizedByParent => switch (fit) {
683 OverflowBoxFit.max => true,
684 // If deferToChild, the size will be as small as its child when non-overflowing,
685 // thus it cannot be sizedByParent.
686 OverflowBoxFit.deferToChild => false,
687 };
688
689 @override
690 @protected
691 Size computeDryLayout(covariant BoxConstraints constraints) {
692 return switch (fit) {
693 OverflowBoxFit.max => constraints.biggest,
694 OverflowBoxFit.deferToChild => child?.getDryLayout(constraints) ?? constraints.smallest,
695 };
696 }
697
698 @override
699 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
700 final RenderBox? child = this.child;
701 if (child == null) {
702 return null;
703 }
704 final BoxConstraints childConstraints = _getInnerConstraints(constraints);
705 final double? result = child.getDryBaseline(childConstraints, baseline);
706 if (result == null) {
707 return null;
708 }
709 final Size childSize = child.getDryLayout(childConstraints);
710 final Size size = getDryLayout(constraints);
711 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
712 }
713
714 @override
715 void performLayout() {
716 if (child != null) {
717 child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
718 switch (fit) {
719 case OverflowBoxFit.max:
720 assert(sizedByParent);
721 case OverflowBoxFit.deferToChild:
722 size = constraints.constrain(child!.size);
723 }
724 alignChild();
725 } else {
726 switch (fit) {
727 case OverflowBoxFit.max:
728 assert(sizedByParent);
729 case OverflowBoxFit.deferToChild:
730 size = constraints.smallest;
731 }
732 }
733 }
734
735 @override
736 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
737 super.debugFillProperties(properties);
738 properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
739 properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
740 properties.add(
741 DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'),
742 );
743 properties.add(
744 DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'),
745 );
746 properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
747 }
748}
749
750/// A [RenderBox] that applies an arbitrary transform to its constraints,
751/// and sizes its child using the resulting [BoxConstraints], optionally
752/// clipping, or treating the overflow as an error.
753///
754/// This [RenderBox] sizes its child using a [BoxConstraints] created by
755/// applying [constraintsTransform] to this [RenderBox]'s own [constraints].
756/// This box will then attempt to adopt the same size, within the limits of its
757/// own constraints. If it ends up with a different size, it will align the
758/// child based on [alignment]. If the box cannot expand enough to accommodate
759/// the entire child, the child will be clipped if [clipBehavior] is not
760/// [Clip.none].
761///
762/// In debug mode, if [clipBehavior] is [Clip.none] and the child overflows the
763/// container, a warning will be printed on the console, and black and yellow
764/// striped areas will appear where the overflow occurs.
765///
766/// When [child] is null, this [RenderBox] takes the smallest possible size and
767/// never overflows.
768///
769/// This [RenderBox] can be used to ensure some of [child]'s natural dimensions
770/// are honored, and get an early warning during development otherwise. For
771/// instance, if [child] requires a minimum height to fully display its content,
772/// [constraintsTransform] can be set to a function that removes the `maxHeight`
773/// constraint from the incoming [BoxConstraints], so that if the parent
774/// [RenderObject] fails to provide enough vertical space, a warning will be
775/// displayed in debug mode, while still allowing [child] to grow vertically.
776///
777/// See also:
778///
779/// * [ConstraintsTransformBox], the widget that makes use of this
780/// [RenderObject] and exposes the same functionality.
781/// * [RenderConstrainedBox], which renders a box which imposes constraints
782/// on its child.
783/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
784/// constraints on its child than it gets from its parent, possibly allowing
785/// the child to overflow the parent.
786/// * [RenderConstraintsTransformBox] for a render object that applies an
787/// arbitrary transform to its constraints before sizing its child using
788/// the new constraints, treating any overflow as error.
789class RenderConstraintsTransformBox extends RenderAligningShiftedBox
790 with DebugOverflowIndicatorMixin {
791 /// Creates a [RenderBox] that sizes itself to the child and modifies the
792 /// [constraints] before passing it down to that child.
793 RenderConstraintsTransformBox({
794 required super.alignment,
795 required super.textDirection,
796 required BoxConstraintsTransform constraintsTransform,
797 super.child,
798 Clip clipBehavior = Clip.none,
799 }) : _constraintsTransform = constraintsTransform,
800 _clipBehavior = clipBehavior;
801
802 /// {@macro flutter.widgets.constraintsTransform}
803 BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
804 BoxConstraintsTransform _constraintsTransform;
805 set constraintsTransform(BoxConstraintsTransform value) {
806 if (_constraintsTransform == value) {
807 return;
808 }
809 _constraintsTransform = value;
810 // The RenderObject only needs layout if the new transform maps the current
811 // `constraints` to a different value, or the render object has never been
812 // laid out before.
813 final bool needsLayout = _childConstraints == null || _childConstraints != value(constraints);
814 if (needsLayout) {
815 markNeedsLayout();
816 }
817 }
818
819 /// {@macro flutter.material.Material.clipBehavior}
820 ///
821 /// {@macro flutter.widgets.ConstraintsTransformBox.clipBehavior}
822 ///
823 /// Defaults to [Clip.none].
824 Clip get clipBehavior => _clipBehavior;
825 Clip _clipBehavior;
826 set clipBehavior(Clip value) {
827 if (value != _clipBehavior) {
828 _clipBehavior = value;
829 markNeedsPaint();
830 markNeedsSemanticsUpdate();
831 }
832 }
833
834 @override
835 double computeMinIntrinsicHeight(double width) {
836 return super.computeMinIntrinsicHeight(
837 constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
838 );
839 }
840
841 @override
842 double computeMaxIntrinsicHeight(double width) {
843 return super.computeMaxIntrinsicHeight(
844 constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
845 );
846 }
847
848 @override
849 double computeMinIntrinsicWidth(double height) {
850 return super.computeMinIntrinsicWidth(
851 constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
852 );
853 }
854
855 @override
856 double computeMaxIntrinsicWidth(double height) {
857 return super.computeMaxIntrinsicWidth(
858 constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
859 );
860 }
861
862 @override
863 @protected
864 Size computeDryLayout(covariant BoxConstraints constraints) {
865 final Size? childSize = child?.getDryLayout(constraintsTransform(constraints));
866 return childSize == null ? constraints.smallest : constraints.constrain(childSize);
867 }
868
869 @override
870 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
871 final RenderBox? child = this.child;
872 if (child == null) {
873 return null;
874 }
875 final BoxConstraints childConstraints = constraintsTransform(constraints);
876 final double? result = child.getDryBaseline(childConstraints, baseline);
877 if (result == null) {
878 return null;
879 }
880 final Size childSize = child.getDryLayout(childConstraints);
881 final Size size = constraints.constrain(childSize);
882 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
883 }
884
885 Rect _overflowContainerRect = Rect.zero;
886 Rect _overflowChildRect = Rect.zero;
887 bool _isOverflowing = false;
888
889 BoxConstraints? _childConstraints;
890
891 @override
892 void performLayout() {
893 final BoxConstraints constraints = this.constraints;
894 final RenderBox? child = this.child;
895 if (child != null) {
896 final BoxConstraints childConstraints = constraintsTransform(constraints);
897 assert(childConstraints.isNormalized, '$childConstraints is not normalized');
898 _childConstraints = childConstraints;
899 child.layout(childConstraints, parentUsesSize: true);
900 size = constraints.constrain(child.size);
901 alignChild();
902 final BoxParentData childParentData = child.parentData! as BoxParentData;
903 _overflowContainerRect = Offset.zero & size;
904 _overflowChildRect = childParentData.offset & child.size;
905 } else {
906 size = constraints.smallest;
907 _overflowContainerRect = Rect.zero;
908 _overflowChildRect = Rect.zero;
909 }
910 _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
911 }
912
913 @override
914 void paint(PaintingContext context, Offset offset) {
915 if (child == null) {
916 return;
917 }
918
919 if (!_isOverflowing) {
920 super.paint(context, offset);
921 return;
922 }
923
924 // We have overflow and the clipBehavior isn't none. Clip it.
925 _clipRectLayer.layer = context.pushClipRect(
926 needsCompositing,
927 offset,
928 Offset.zero & size,
929 super.paint,
930 clipBehavior: clipBehavior,
931 oldLayer: _clipRectLayer.layer,
932 );
933
934 // Display the overflow indicator if clipBehavior is Clip.none.
935 assert(() {
936 if (size.isEmpty) {
937 return true;
938 }
939 switch (clipBehavior) {
940 case Clip.none:
941 paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
942 case Clip.hardEdge:
943 case Clip.antiAlias:
944 case Clip.antiAliasWithSaveLayer:
945 break;
946 }
947 return true;
948 }());
949 }
950
951 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
952
953 @override
954 void dispose() {
955 _clipRectLayer.layer = null;
956 super.dispose();
957 }
958
959 @override
960 Rect? describeApproximatePaintClip(RenderObject child) {
961 switch (clipBehavior) {
962 case Clip.none:
963 return null;
964 case Clip.hardEdge:
965 case Clip.antiAlias:
966 case Clip.antiAliasWithSaveLayer:
967 return _isOverflowing ? Offset.zero & size : null;
968 }
969 }
970
971 @override
972 String toStringShort() {
973 String header = super.toStringShort();
974 if (!kReleaseMode) {
975 if (_isOverflowing) {
976 header += ' OVERFLOWING';
977 }
978 }
979 return header;
980 }
981}
982
983/// A render object that is a specific size but passes its original constraints
984/// through to its child, which it allows to overflow.
985///
986/// If the child's resulting size differs from this render object's size, then
987/// the child is aligned according to the [alignment] property.
988///
989/// See also:
990///
991/// * [RenderConstraintsTransformBox] for a render object that applies an
992/// arbitrary transform to its constraints before sizing its child using
993/// the new constraints, treating any overflow as error.
994/// * [RenderConstrainedOverflowBox] for a render object that imposes
995/// different constraints on its child than it gets from its parent,
996/// possibly allowing the child to overflow the parent.
997class RenderSizedOverflowBox extends RenderAligningShiftedBox {
998 /// Creates a render box of a given size that lets its child overflow.
999 ///
1000 /// The [textDirection] argument must not be null if the [alignment] is
1001 /// direction-sensitive.
1002 RenderSizedOverflowBox({
1003 super.child,
1004 required Size requestedSize,
1005 super.alignment,
1006 super.textDirection,
1007 }) : _requestedSize = requestedSize;
1008
1009 /// The size this render box should attempt to be.
1010 Size get requestedSize => _requestedSize;
1011 Size _requestedSize;
1012 set requestedSize(Size value) {
1013 if (_requestedSize == value) {
1014 return;
1015 }
1016 _requestedSize = value;
1017 markNeedsLayout();
1018 }
1019
1020 @override
1021 double computeMinIntrinsicWidth(double height) {
1022 return _requestedSize.width;
1023 }
1024
1025 @override
1026 double computeMaxIntrinsicWidth(double height) {
1027 return _requestedSize.width;
1028 }
1029
1030 @override
1031 double computeMinIntrinsicHeight(double width) {
1032 return _requestedSize.height;
1033 }
1034
1035 @override
1036 double computeMaxIntrinsicHeight(double width) {
1037 return _requestedSize.height;
1038 }
1039
1040 @override
1041 double? computeDistanceToActualBaseline(TextBaseline baseline) {
1042 return child?.getDistanceToActualBaseline(baseline) ??
1043 super.computeDistanceToActualBaseline(baseline);
1044 }
1045
1046 @override
1047 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1048 final RenderBox? child = this.child;
1049 if (child == null) {
1050 return null;
1051 }
1052 final double? result = child.getDryBaseline(constraints, baseline);
1053 if (result == null) {
1054 return null;
1055 }
1056 final Size childSize = child.getDryLayout(constraints);
1057 final Size size = getDryLayout(constraints);
1058 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
1059 }
1060
1061 @override
1062 @protected
1063 Size computeDryLayout(covariant BoxConstraints constraints) {
1064 return constraints.constrain(_requestedSize);
1065 }
1066
1067 @override
1068 void performLayout() {
1069 size = constraints.constrain(_requestedSize);
1070 if (child != null) {
1071 child!.layout(constraints, parentUsesSize: true);
1072 alignChild();
1073 }
1074 }
1075}
1076
1077/// Sizes its child to a fraction of the total available space.
1078///
1079/// For both its width and height, this render object imposes a tight
1080/// constraint on its child that is a multiple (typically less than 1.0) of the
1081/// maximum constraint it received from its parent on that axis. If the factor
1082/// for a given axis is null, then the constraints from the parent are just
1083/// passed through instead.
1084///
1085/// It then tries to size itself to the size of its child. Where this is not
1086/// possible (e.g. if the constraints from the parent are themselves tight), the
1087/// child is aligned according to [alignment].
1088class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
1089 /// Creates a render box that sizes its child to a fraction of the total available space.
1090 ///
1091 /// If non-null, the [widthFactor] and [heightFactor] arguments must be
1092 /// non-negative.
1093 ///
1094 /// The [textDirection] must be non-null if the [alignment] is
1095 /// direction-sensitive.
1096 RenderFractionallySizedOverflowBox({
1097 super.child,
1098 double? widthFactor,
1099 double? heightFactor,
1100 super.alignment,
1101 super.textDirection,
1102 }) : _widthFactor = widthFactor,
1103 _heightFactor = heightFactor {
1104 assert(_widthFactor == null || _widthFactor! >= 0.0);
1105 assert(_heightFactor == null || _heightFactor! >= 0.0);
1106 }
1107
1108 /// If non-null, the factor of the incoming width to use.
1109 ///
1110 /// If non-null, the child is given a tight width constraint that is the max
1111 /// incoming width constraint multiplied by this factor. If null, the child is
1112 /// given the incoming width constraints.
1113 double? get widthFactor => _widthFactor;
1114 double? _widthFactor;
1115 set widthFactor(double? value) {
1116 assert(value == null || value >= 0.0);
1117 if (_widthFactor == value) {
1118 return;
1119 }
1120 _widthFactor = value;
1121 markNeedsLayout();
1122 }
1123
1124 /// If non-null, the factor of the incoming height to use.
1125 ///
1126 /// If non-null, the child is given a tight height constraint that is the max
1127 /// incoming width constraint multiplied by this factor. If null, the child is
1128 /// given the incoming width constraints.
1129 double? get heightFactor => _heightFactor;
1130 double? _heightFactor;
1131 set heightFactor(double? value) {
1132 assert(value == null || value >= 0.0);
1133 if (_heightFactor == value) {
1134 return;
1135 }
1136 _heightFactor = value;
1137 markNeedsLayout();
1138 }
1139
1140 BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
1141 double minWidth = constraints.minWidth;
1142 double maxWidth = constraints.maxWidth;
1143 if (_widthFactor != null) {
1144 final double width = maxWidth * _widthFactor!;
1145 minWidth = width;
1146 maxWidth = width;
1147 }
1148 double minHeight = constraints.minHeight;
1149 double maxHeight = constraints.maxHeight;
1150 if (_heightFactor != null) {
1151 final double height = maxHeight * _heightFactor!;
1152 minHeight = height;
1153 maxHeight = height;
1154 }
1155 return BoxConstraints(
1156 minWidth: minWidth,
1157 maxWidth: maxWidth,
1158 minHeight: minHeight,
1159 maxHeight: maxHeight,
1160 );
1161 }
1162
1163 @override
1164 double computeMinIntrinsicWidth(double height) {
1165 final double result;
1166 if (child == null) {
1167 result = super.computeMinIntrinsicWidth(height);
1168 } else {
1169 // the following line relies on double.infinity absorption
1170 result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
1171 }
1172 assert(result.isFinite);
1173 return result / (_widthFactor ?? 1.0);
1174 }
1175
1176 @override
1177 double computeMaxIntrinsicWidth(double height) {
1178 final double result;
1179 if (child == null) {
1180 result = super.computeMaxIntrinsicWidth(height);
1181 } else {
1182 // the following line relies on double.infinity absorption
1183 result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
1184 }
1185 assert(result.isFinite);
1186 return result / (_widthFactor ?? 1.0);
1187 }
1188
1189 @override
1190 double computeMinIntrinsicHeight(double width) {
1191 final double result;
1192 if (child == null) {
1193 result = super.computeMinIntrinsicHeight(width);
1194 } else {
1195 // the following line relies on double.infinity absorption
1196 result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
1197 }
1198 assert(result.isFinite);
1199 return result / (_heightFactor ?? 1.0);
1200 }
1201
1202 @override
1203 double computeMaxIntrinsicHeight(double width) {
1204 final double result;
1205 if (child == null) {
1206 result = super.computeMaxIntrinsicHeight(width);
1207 } else {
1208 // the following line relies on double.infinity absorption
1209 result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
1210 }
1211 assert(result.isFinite);
1212 return result / (_heightFactor ?? 1.0);
1213 }
1214
1215 @override
1216 @protected
1217 Size computeDryLayout(covariant BoxConstraints constraints) {
1218 if (child != null) {
1219 final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
1220 return constraints.constrain(childSize);
1221 }
1222 return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
1223 }
1224
1225 @override
1226 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1227 final RenderBox? child = this.child;
1228 if (child == null) {
1229 return null;
1230 }
1231 final BoxConstraints childConstraints = _getInnerConstraints(constraints);
1232 final double? result = child.getDryBaseline(childConstraints, baseline);
1233 if (result == null) {
1234 return null;
1235 }
1236 final Size childSize = child.getDryLayout(childConstraints);
1237 final Size size = getDryLayout(constraints);
1238 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
1239 }
1240
1241 @override
1242 void performLayout() {
1243 if (child != null) {
1244 child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
1245 size = constraints.constrain(child!.size);
1246 alignChild();
1247 } else {
1248 size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
1249 }
1250 }
1251
1252 @override
1253 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1254 super.debugFillProperties(properties);
1255 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
1256 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
1257 }
1258}
1259
1260/// A delegate for computing the layout of a render object with a single child.
1261///
1262/// Used by [CustomSingleChildLayout] (in the widgets library) and
1263/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
1264///
1265/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
1266/// its incoming constraints to determine its size. It then calls
1267/// [getConstraintsForChild] to determine the constraints to apply to the child.
1268/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
1269/// calls [getPositionForChild] to determine the child's position.
1270///
1271/// The [shouldRelayout] method is called when a new instance of the class
1272/// is provided, to check if the new instance actually represents different
1273/// information.
1274///
1275/// The most efficient way to trigger a relayout is to supply a `relayout`
1276/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
1277/// layout will listen to this value and relayout whenever the Listenable
1278/// notifies its listeners, such as when an [Animation] ticks. This allows
1279/// the custom layout to avoid the build phase of the pipeline.
1280///
1281/// See also:
1282///
1283/// * [CustomSingleChildLayout], the widget that uses this delegate.
1284/// * [RenderCustomSingleChildLayoutBox], render object that uses this
1285/// delegate.
1286abstract class SingleChildLayoutDelegate {
1287 /// Creates a layout delegate.
1288 ///
1289 /// The layout will update whenever [relayout] notifies its listeners.
1290 const SingleChildLayoutDelegate({Listenable? relayout}) : _relayout = relayout;
1291
1292 final Listenable? _relayout;
1293
1294 /// The size of this object given the incoming constraints.
1295 ///
1296 /// Defaults to the biggest size that satisfies the given constraints.
1297 Size getSize(BoxConstraints constraints) => constraints.biggest;
1298
1299 /// The constraints for the child given the incoming constraints.
1300 ///
1301 /// During layout, the child is given the layout constraints returned by this
1302 /// function. The child is required to pick a size for itself that satisfies
1303 /// these constraints.
1304 ///
1305 /// Defaults to the given constraints.
1306 BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
1307
1308 /// The position where the child should be placed.
1309 ///
1310 /// The `size` argument is the size of the parent, which might be different
1311 /// from the value returned by [getSize] if that size doesn't satisfy the
1312 /// constraints passed to [getSize]. The `childSize` argument is the size of
1313 /// the child, which will satisfy the constraints returned by
1314 /// [getConstraintsForChild].
1315 ///
1316 /// Defaults to positioning the child in the upper left corner of the parent.
1317 Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1318
1319 /// Called whenever a new instance of the custom layout delegate class is
1320 /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
1321 /// that a new [CustomSingleChildLayout] object is created with a new instance
1322 /// of the custom layout delegate class (which amounts to the same thing,
1323 /// because the latter is implemented in terms of the former).
1324 ///
1325 /// If the new instance represents different information than the old
1326 /// instance, then the method should return true, otherwise it should return
1327 /// false.
1328 ///
1329 /// If the method returns false, then the [getSize],
1330 /// [getConstraintsForChild], and [getPositionForChild] calls might be
1331 /// optimized away.
1332 ///
1333 /// It's possible that the layout methods will get called even if
1334 /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1335 /// It's also possible that the layout method will get called
1336 /// without [shouldRelayout] being called at all (e.g. if the parent changes
1337 /// size).
1338 bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
1339}
1340
1341/// Defers the layout of its single child to a delegate.
1342///
1343/// The delegate can determine the layout constraints for the child and can
1344/// decide where to position the child. The delegate can also determine the size
1345/// of the parent, but the size of the parent cannot depend on the size of the
1346/// child.
1347class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1348 /// Creates a render box that defers its layout to a delegate.
1349 ///
1350 /// The [delegate] argument must not be null.
1351 RenderCustomSingleChildLayoutBox({RenderBox? child, required SingleChildLayoutDelegate delegate})
1352 : _delegate = delegate,
1353 super(child);
1354
1355 /// A delegate that controls this object's layout.
1356 SingleChildLayoutDelegate get delegate => _delegate;
1357 SingleChildLayoutDelegate _delegate;
1358 set delegate(SingleChildLayoutDelegate newDelegate) {
1359 if (_delegate == newDelegate) {
1360 return;
1361 }
1362 final SingleChildLayoutDelegate oldDelegate = _delegate;
1363 if (newDelegate.runtimeType != oldDelegate.runtimeType ||
1364 newDelegate.shouldRelayout(oldDelegate)) {
1365 markNeedsLayout();
1366 }
1367 _delegate = newDelegate;
1368 if (attached) {
1369 oldDelegate._relayout?.removeListener(markNeedsLayout);
1370 newDelegate._relayout?.addListener(markNeedsLayout);
1371 }
1372 }
1373
1374 @override
1375 void attach(PipelineOwner owner) {
1376 super.attach(owner);
1377 _delegate._relayout?.addListener(markNeedsLayout);
1378 }
1379
1380 @override
1381 void detach() {
1382 _delegate._relayout?.removeListener(markNeedsLayout);
1383 super.detach();
1384 }
1385
1386 Size _getSize(BoxConstraints constraints) {
1387 return constraints.constrain(_delegate.getSize(constraints));
1388 }
1389
1390 // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
1391 // figure out the intrinsic dimensions. We really should either not support intrinsics,
1392 // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
1393
1394 @override
1395 double computeMinIntrinsicWidth(double height) {
1396 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1397 if (width.isFinite) {
1398 return width;
1399 }
1400 return 0.0;
1401 }
1402
1403 @override
1404 double computeMaxIntrinsicWidth(double height) {
1405 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1406 if (width.isFinite) {
1407 return width;
1408 }
1409 return 0.0;
1410 }
1411
1412 @override
1413 double computeMinIntrinsicHeight(double width) {
1414 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1415 if (height.isFinite) {
1416 return height;
1417 }
1418 return 0.0;
1419 }
1420
1421 @override
1422 double computeMaxIntrinsicHeight(double width) {
1423 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1424 if (height.isFinite) {
1425 return height;
1426 }
1427 return 0.0;
1428 }
1429
1430 @override
1431 @protected
1432 Size computeDryLayout(covariant BoxConstraints constraints) {
1433 return _getSize(constraints);
1434 }
1435
1436 @override
1437 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1438 final RenderBox? child = this.child;
1439 if (child == null) {
1440 return null;
1441 }
1442 final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1443 final double? result = child.getDryBaseline(childConstraints, baseline);
1444 if (result == null) {
1445 return null;
1446 }
1447 return result +
1448 delegate
1449 .getPositionForChild(
1450 _getSize(constraints),
1451 childConstraints.isTight
1452 ? childConstraints.smallest
1453 : child.getDryLayout(childConstraints),
1454 )
1455 .dy;
1456 }
1457
1458 @override
1459 void performLayout() {
1460 size = _getSize(constraints);
1461 if (child != null) {
1462 final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1463 assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1464 child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1465 final BoxParentData childParentData = child!.parentData! as BoxParentData;
1466 childParentData.offset = delegate.getPositionForChild(
1467 size,
1468 childConstraints.isTight ? childConstraints.smallest : child!.size,
1469 );
1470 }
1471 }
1472}
1473
1474/// Shifts the child down such that the child's baseline (or the
1475/// bottom of the child, if the child has no baseline) is [baseline]
1476/// logical pixels below the top of this box, then sizes this box to
1477/// contain the child.
1478///
1479/// If [baseline] is less than the distance from the top of the child
1480/// to the baseline of the child, then the child will overflow the top
1481/// of the box. This is typically not desirable, in particular, that
1482/// part of the child will not be found when doing hit tests, so the
1483/// user cannot interact with that part of the child.
1484///
1485/// This box will be sized so that its bottom is coincident with the
1486/// bottom of the child. This means if this box shifts the child down,
1487/// there will be space between the top of this box and the top of the
1488/// child, but there is never space between the bottom of the child
1489/// and the bottom of the box.
1490class RenderBaseline extends RenderShiftedBox {
1491 /// Creates a [RenderBaseline] object.
1492 RenderBaseline({RenderBox? child, required double baseline, required TextBaseline baselineType})
1493 : _baseline = baseline,
1494 _baselineType = baselineType,
1495 super(child);
1496
1497 /// The number of logical pixels from the top of this box at which to position
1498 /// the child's baseline.
1499 double get baseline => _baseline;
1500 double _baseline;
1501 set baseline(double value) {
1502 if (_baseline == value) {
1503 return;
1504 }
1505 _baseline = value;
1506 markNeedsLayout();
1507 }
1508
1509 /// The type of baseline to use for positioning the child.
1510 TextBaseline get baselineType => _baselineType;
1511 TextBaseline _baselineType;
1512 set baselineType(TextBaseline value) {
1513 if (_baselineType == value) {
1514 return;
1515 }
1516 _baselineType = value;
1517 markNeedsLayout();
1518 }
1519
1520 ({Size size, double top}) _computeSizes(
1521 covariant BoxConstraints constraints,
1522 ChildLayouter layoutChild,
1523 ChildBaselineGetter getBaseline,
1524 ) {
1525 final RenderBox? child = this.child;
1526 if (child == null) {
1527 return (size: constraints.smallest, top: 0);
1528 }
1529 final BoxConstraints childConstraints = constraints.loosen();
1530 final Size childSize = layoutChild(child, childConstraints);
1531 final double childBaseline =
1532 getBaseline(child, childConstraints, baselineType) ?? childSize.height;
1533 final double top = baseline - childBaseline;
1534 return (size: constraints.constrain(Size(childSize.width, top + childSize.height)), top: top);
1535 }
1536
1537 @override
1538 @protected
1539 Size computeDryLayout(covariant BoxConstraints constraints) {
1540 return _computeSizes(
1541 constraints,
1542 ChildLayoutHelper.dryLayoutChild,
1543 ChildLayoutHelper.getDryBaseline,
1544 ).size;
1545 }
1546
1547 @override
1548 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1549 final RenderBox? child = this.child;
1550 final double? result1 = child?.getDryBaseline(constraints.loosen(), baseline);
1551 final double? result2 = child?.getDryBaseline(constraints.loosen(), baselineType);
1552 if (result1 == null || result2 == null) {
1553 return null;
1554 }
1555 return this.baseline + result1 - result2;
1556 }
1557
1558 @override
1559 void performLayout() {
1560 final (:Size size, :double top) = _computeSizes(
1561 constraints,
1562 ChildLayoutHelper.layoutChild,
1563 ChildLayoutHelper.getBaseline,
1564 );
1565 this.size = size;
1566 (child?.parentData as BoxParentData?)?.offset = Offset(0.0, top);
1567 }
1568
1569 @override
1570 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1571 super.debugFillProperties(properties);
1572 properties.add(DoubleProperty('baseline', baseline));
1573 properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1574 }
1575}
1576