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