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 =
488 Paint()
489 ..style = PaintingStyle.stroke
490 ..strokeWidth = 1.0
491 ..color = const Color(0xFFFFFF00);
492 path = Path();
493 final BoxParentData childParentData = child!.parentData! as BoxParentData;
494 if (childParentData.offset.dy > 0.0) {
495 // vertical alignment arrows
496 final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
497 path
498 ..moveTo(offset.dx + size.width / 2.0, offset.dy)
499 ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
500 ..relativeLineTo(headSize, 0.0)
501 ..relativeLineTo(-headSize, headSize)
502 ..relativeLineTo(-headSize, -headSize)
503 ..relativeLineTo(headSize, 0.0)
504 ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
505 ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
506 ..relativeLineTo(headSize, 0.0)
507 ..relativeLineTo(-headSize, -headSize)
508 ..relativeLineTo(-headSize, headSize)
509 ..relativeLineTo(headSize, 0.0);
510 context.canvas.drawPath(path, paint);
511 }
512 if (childParentData.offset.dx > 0.0) {
513 // horizontal alignment arrows
514 final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
515 path
516 ..moveTo(offset.dx, offset.dy + size.height / 2.0)
517 ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
518 ..relativeLineTo(0.0, headSize)
519 ..relativeLineTo(headSize, -headSize)
520 ..relativeLineTo(-headSize, -headSize)
521 ..relativeLineTo(0.0, headSize)
522 ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
523 ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
524 ..relativeLineTo(0.0, headSize)
525 ..relativeLineTo(-headSize, -headSize)
526 ..relativeLineTo(headSize, -headSize)
527 ..relativeLineTo(0.0, headSize);
528 context.canvas.drawPath(path, paint);
529 }
530 } else {
531 paint = Paint()..color = const Color(0x90909090);
532 context.canvas.drawRect(offset & size, paint);
533 }
534 return true;
535 }());
536 }
537
538 @override
539 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
540 super.debugFillProperties(properties);
541 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
542 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
543 }
544}
545
546/// How much space should be occupied by the [OverflowBox] if there is no
547/// overflow.
548enum OverflowBoxFit {
549 /// The widget will size itself to be as large as the parent allows.
550 max,
551
552 /// The widget will follow the child's size.
553 ///
554 /// More specifically, the render object will size itself to match the size of
555 /// its child within the constraints of its parent, or as small as the
556 /// parent allows if no child is set.
557 deferToChild,
558}
559
560/// A render object that imposes different constraints on its child than it gets
561/// from its parent, possibly allowing the child to overflow the parent.
562///
563/// A render overflow box proxies most functions in the render box protocol to
564/// its child, except that when laying out its child, it passes constraints
565/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
566/// just passing the parent's constraints in. Specifically, it overrides any of
567/// the equivalent fields on the constraints given by the parent with the
568/// constraints given by these fields for each such field that is not null. It
569/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
570/// ignoring the child's dimensions.
571///
572/// For example, if you wanted a box to always render 50 pixels high, regardless
573/// of where it was rendered, you would wrap it in a
574/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
575/// Generally speaking, to avoid confusing behavior around hit testing, a
576/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
577///
578/// The child is positioned according to [alignment]. To position a smaller
579/// child inside a larger parent, use [RenderPositionedBox] and
580/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
581///
582/// See also:
583///
584/// * [RenderConstraintsTransformBox] for a render object that applies an
585/// arbitrary transform to its constraints before sizing its child using
586/// the new constraints, treating any overflow as error.
587/// * [RenderSizedOverflowBox], a render object that is a specific size but
588/// passes its original constraints through to its child, which it allows to
589/// overflow.
590class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
591 /// Creates a render object that lets its child overflow itself.
592 RenderConstrainedOverflowBox({
593 super.child,
594 double? minWidth,
595 double? maxWidth,
596 double? minHeight,
597 double? maxHeight,
598 OverflowBoxFit fit = OverflowBoxFit.max,
599 super.alignment,
600 super.textDirection,
601 }) : _minWidth = minWidth,
602 _maxWidth = maxWidth,
603 _minHeight = minHeight,
604 _maxHeight = maxHeight,
605 _fit = fit;
606
607 /// The minimum width constraint to give the child. Set this to null (the
608 /// default) to use the constraint from the parent instead.
609 double? get minWidth => _minWidth;
610 double? _minWidth;
611 set minWidth(double? value) {
612 if (_minWidth == value) {
613 return;
614 }
615 _minWidth = value;
616 markNeedsLayout();
617 }
618
619 /// The maximum width constraint to give the child. Set this to null (the
620 /// default) to use the constraint from the parent instead.
621 double? get maxWidth => _maxWidth;
622 double? _maxWidth;
623 set maxWidth(double? value) {
624 if (_maxWidth == value) {
625 return;
626 }
627 _maxWidth = value;
628 markNeedsLayout();
629 }
630
631 /// The minimum height constraint to give the child. Set this to null (the
632 /// default) to use the constraint from the parent instead.
633 double? get minHeight => _minHeight;
634 double? _minHeight;
635 set minHeight(double? value) {
636 if (_minHeight == value) {
637 return;
638 }
639 _minHeight = value;
640 markNeedsLayout();
641 }
642
643 /// The maximum height constraint to give the child. Set this to null (the
644 /// default) to use the constraint from the parent instead.
645 double? get maxHeight => _maxHeight;
646 double? _maxHeight;
647 set maxHeight(double? value) {
648 if (_maxHeight == value) {
649 return;
650 }
651 _maxHeight = value;
652 markNeedsLayout();
653 }
654
655 /// The way to size the render object.
656 ///
657 /// This only affects scenario when the child does not indeed overflow.
658 /// If set to [OverflowBoxFit.deferToChild], the render object will size
659 /// itself to match the size of its child within the constraints of its
660 /// parent, or as small as the parent allows if no child is set.
661 /// If set to [OverflowBoxFit.max] (the default), the
662 /// render object will size itself to be as large as the parent allows.
663 OverflowBoxFit get fit => _fit;
664 OverflowBoxFit _fit;
665 set fit(OverflowBoxFit value) {
666 if (_fit == value) {
667 return;
668 }
669 _fit = value;
670 markNeedsLayoutForSizedByParentChange();
671 }
672
673 BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
674 return BoxConstraints(
675 minWidth: _minWidth ?? constraints.minWidth,
676 maxWidth: _maxWidth ?? constraints.maxWidth,
677 minHeight: _minHeight ?? constraints.minHeight,
678 maxHeight: _maxHeight ?? constraints.maxHeight,
679 );
680 }
681
682 @override
683 bool get sizedByParent => switch (fit) {
684 OverflowBoxFit.max => true,
685 // If deferToChild, the size will be as small as its child when non-overflowing,
686 // thus it cannot be sizedByParent.
687 OverflowBoxFit.deferToChild => false,
688 };
689
690 @override
691 @protected
692 Size computeDryLayout(covariant BoxConstraints constraints) {
693 return switch (fit) {
694 OverflowBoxFit.max => constraints.biggest,
695 OverflowBoxFit.deferToChild => child?.getDryLayout(constraints) ?? constraints.smallest,
696 };
697 }
698
699 @override
700 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
701 final RenderBox? child = this.child;
702 if (child == null) {
703 return null;
704 }
705 final BoxConstraints childConstraints = _getInnerConstraints(constraints);
706 final double? result = child.getDryBaseline(childConstraints, baseline);
707 if (result == null) {
708 return null;
709 }
710 final Size childSize = child.getDryLayout(childConstraints);
711 final Size size = getDryLayout(constraints);
712 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
713 }
714
715 @override
716 void performLayout() {
717 if (child != null) {
718 child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
719 switch (fit) {
720 case OverflowBoxFit.max:
721 assert(sizedByParent);
722 case OverflowBoxFit.deferToChild:
723 size = constraints.constrain(child!.size);
724 }
725 alignChild();
726 } else {
727 switch (fit) {
728 case OverflowBoxFit.max:
729 assert(sizedByParent);
730 case OverflowBoxFit.deferToChild:
731 size = constraints.smallest;
732 }
733 }
734 }
735
736 @override
737 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
738 super.debugFillProperties(properties);
739 properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
740 properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
741 properties.add(
742 DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'),
743 );
744 properties.add(
745 DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'),
746 );
747 properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
748 }
749}
750
751/// A [RenderBox] that applies an arbitrary transform to its constraints,
752/// and sizes its child using the resulting [BoxConstraints], optionally
753/// clipping, or treating the overflow as an error.
754///
755/// This [RenderBox] sizes its child using a [BoxConstraints] created by
756/// applying [constraintsTransform] to this [RenderBox]'s own [constraints].
757/// This box will then attempt to adopt the same size, within the limits of its
758/// own constraints. If it ends up with a different size, it will align the
759/// child based on [alignment]. If the box cannot expand enough to accommodate
760/// the entire child, the child will be clipped if [clipBehavior] is not
761/// [Clip.none].
762///
763/// In debug mode, if [clipBehavior] is [Clip.none] and the child overflows the
764/// container, a warning will be printed on the console, and black and yellow
765/// striped areas will appear where the overflow occurs.
766///
767/// When [child] is null, this [RenderBox] takes the smallest possible size and
768/// never overflows.
769///
770/// This [RenderBox] can be used to ensure some of [child]'s natural dimensions
771/// are honored, and get an early warning during development otherwise. For
772/// instance, if [child] requires a minimum height to fully display its content,
773/// [constraintsTransform] can be set to a function that removes the `maxHeight`
774/// constraint from the incoming [BoxConstraints], so that if the parent
775/// [RenderObject] fails to provide enough vertical space, a warning will be
776/// displayed in debug mode, while still allowing [child] to grow vertically.
777///
778/// See also:
779///
780/// * [ConstraintsTransformBox], the widget that makes use of this
781/// [RenderObject] and exposes the same functionality.
782/// * [RenderConstrainedBox], which renders a box which imposes constraints
783/// on its child.
784/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
785/// constraints on its child than it gets from its parent, possibly allowing
786/// the child to overflow the parent.
787/// * [RenderConstraintsTransformBox] for a render object that applies an
788/// arbitrary transform to its constraints before sizing its child using
789/// the new constraints, treating any overflow as error.
790class RenderConstraintsTransformBox extends RenderAligningShiftedBox
791 with DebugOverflowIndicatorMixin {
792 /// Creates a [RenderBox] that sizes itself to the child and modifies the
793 /// [constraints] before passing it down to that child.
794 RenderConstraintsTransformBox({
795 required super.alignment,
796 required super.textDirection,
797 required BoxConstraintsTransform constraintsTransform,
798 super.child,
799 Clip clipBehavior = Clip.none,
800 }) : _constraintsTransform = constraintsTransform,
801 _clipBehavior = clipBehavior;
802
803 /// {@macro flutter.widgets.constraintsTransform}
804 BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
805 BoxConstraintsTransform _constraintsTransform;
806 set constraintsTransform(BoxConstraintsTransform value) {
807 if (_constraintsTransform == value) {
808 return;
809 }
810 _constraintsTransform = value;
811 // The RenderObject only needs layout if the new transform maps the current
812 // `constraints` to a different value, or the render object has never been
813 // laid out before.
814 final bool needsLayout = _childConstraints == null || _childConstraints != value(constraints);
815 if (needsLayout) {
816 markNeedsLayout();
817 }
818 }
819
820 /// {@macro flutter.material.Material.clipBehavior}
821 ///
822 /// {@macro flutter.widgets.ConstraintsTransformBox.clipBehavior}
823 ///
824 /// Defaults to [Clip.none].
825 Clip get clipBehavior => _clipBehavior;
826 Clip _clipBehavior;
827 set clipBehavior(Clip value) {
828 if (value != _clipBehavior) {
829 _clipBehavior = value;
830 markNeedsPaint();
831 markNeedsSemanticsUpdate();
832 }
833 }
834
835 @override
836 double computeMinIntrinsicHeight(double width) {
837 return super.computeMinIntrinsicHeight(
838 constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
839 );
840 }
841
842 @override
843 double computeMaxIntrinsicHeight(double width) {
844 return super.computeMaxIntrinsicHeight(
845 constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
846 );
847 }
848
849 @override
850 double computeMinIntrinsicWidth(double height) {
851 return super.computeMinIntrinsicWidth(
852 constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
853 );
854 }
855
856 @override
857 double computeMaxIntrinsicWidth(double height) {
858 return super.computeMaxIntrinsicWidth(
859 constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
860 );
861 }
862
863 @override
864 @protected
865 Size computeDryLayout(covariant BoxConstraints constraints) {
866 final Size? childSize = child?.getDryLayout(constraintsTransform(constraints));
867 return childSize == null ? constraints.smallest : constraints.constrain(childSize);
868 }
869
870 @override
871 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
872 final RenderBox? child = this.child;
873 if (child == null) {
874 return null;
875 }
876 final BoxConstraints childConstraints = constraintsTransform(constraints);
877 final double? result = child.getDryBaseline(childConstraints, baseline);
878 if (result == null) {
879 return null;
880 }
881 final Size childSize = child.getDryLayout(childConstraints);
882 final Size size = constraints.constrain(childSize);
883 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
884 }
885
886 Rect _overflowContainerRect = Rect.zero;
887 Rect _overflowChildRect = Rect.zero;
888 bool _isOverflowing = false;
889
890 BoxConstraints? _childConstraints;
891
892 @override
893 void performLayout() {
894 final BoxConstraints constraints = this.constraints;
895 final RenderBox? child = this.child;
896 if (child != null) {
897 final BoxConstraints childConstraints = constraintsTransform(constraints);
898 assert(childConstraints.isNormalized, '$childConstraints is not normalized');
899 _childConstraints = childConstraints;
900 child.layout(childConstraints, parentUsesSize: true);
901 size = constraints.constrain(child.size);
902 alignChild();
903 final BoxParentData childParentData = child.parentData! as BoxParentData;
904 _overflowContainerRect = Offset.zero & size;
905 _overflowChildRect = childParentData.offset & child.size;
906 } else {
907 size = constraints.smallest;
908 _overflowContainerRect = Rect.zero;
909 _overflowChildRect = Rect.zero;
910 }
911 _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
912 }
913
914 @override
915 void paint(PaintingContext context, Offset offset) {
916 if (child == null) {
917 return;
918 }
919
920 if (!_isOverflowing) {
921 super.paint(context, offset);
922 return;
923 }
924
925 // We have overflow and the clipBehavior isn't none. Clip it.
926 _clipRectLayer.layer = context.pushClipRect(
927 needsCompositing,
928 offset,
929 Offset.zero & size,
930 super.paint,
931 clipBehavior: clipBehavior,
932 oldLayer: _clipRectLayer.layer,
933 );
934
935 // Display the overflow indicator if clipBehavior is Clip.none.
936 assert(() {
937 if (size.isEmpty) {
938 return true;
939 }
940 switch (clipBehavior) {
941 case Clip.none:
942 paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
943 case Clip.hardEdge:
944 case Clip.antiAlias:
945 case Clip.antiAliasWithSaveLayer:
946 break;
947 }
948 return true;
949 }());
950 }
951
952 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
953
954 @override
955 void dispose() {
956 _clipRectLayer.layer = null;
957 super.dispose();
958 }
959
960 @override
961 Rect? describeApproximatePaintClip(RenderObject child) {
962 switch (clipBehavior) {
963 case Clip.none:
964 return null;
965 case Clip.hardEdge:
966 case Clip.antiAlias:
967 case Clip.antiAliasWithSaveLayer:
968 return _isOverflowing ? Offset.zero & size : null;
969 }
970 }
971
972 @override
973 String toStringShort() {
974 String header = super.toStringShort();
975 if (!kReleaseMode) {
976 if (_isOverflowing) {
977 header += ' OVERFLOWING';
978 }
979 }
980 return header;
981 }
982}
983
984/// A render object that is a specific size but passes its original constraints
985/// through to its child, which it allows to overflow.
986///
987/// If the child's resulting size differs from this render object's size, then
988/// the child is aligned according to the [alignment] property.
989///
990/// See also:
991///
992/// * [RenderConstraintsTransformBox] for a render object that applies an
993/// arbitrary transform to its constraints before sizing its child using
994/// the new constraints, treating any overflow as error.
995/// * [RenderConstrainedOverflowBox] for a render object that imposes
996/// different constraints on its child than it gets from its parent,
997/// possibly allowing the child to overflow the parent.
998class RenderSizedOverflowBox extends RenderAligningShiftedBox {
999 /// Creates a render box of a given size that lets its child overflow.
1000 ///
1001 /// The [textDirection] argument must not be null if the [alignment] is
1002 /// direction-sensitive.
1003 RenderSizedOverflowBox({
1004 super.child,
1005 required Size requestedSize,
1006 super.alignment,
1007 super.textDirection,
1008 }) : _requestedSize = requestedSize;
1009
1010 /// The size this render box should attempt to be.
1011 Size get requestedSize => _requestedSize;
1012 Size _requestedSize;
1013 set requestedSize(Size value) {
1014 if (_requestedSize == value) {
1015 return;
1016 }
1017 _requestedSize = value;
1018 markNeedsLayout();
1019 }
1020
1021 @override
1022 double computeMinIntrinsicWidth(double height) {
1023 return _requestedSize.width;
1024 }
1025
1026 @override
1027 double computeMaxIntrinsicWidth(double height) {
1028 return _requestedSize.width;
1029 }
1030
1031 @override
1032 double computeMinIntrinsicHeight(double width) {
1033 return _requestedSize.height;
1034 }
1035
1036 @override
1037 double computeMaxIntrinsicHeight(double width) {
1038 return _requestedSize.height;
1039 }
1040
1041 @override
1042 double? computeDistanceToActualBaseline(TextBaseline baseline) {
1043 return child?.getDistanceToActualBaseline(baseline) ??
1044 super.computeDistanceToActualBaseline(baseline);
1045 }
1046
1047 @override
1048 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1049 final RenderBox? child = this.child;
1050 if (child == null) {
1051 return null;
1052 }
1053 final double? result = child.getDryBaseline(constraints, baseline);
1054 if (result == null) {
1055 return null;
1056 }
1057 final Size childSize = child.getDryLayout(constraints);
1058 final Size size = getDryLayout(constraints);
1059 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
1060 }
1061
1062 @override
1063 @protected
1064 Size computeDryLayout(covariant BoxConstraints constraints) {
1065 return constraints.constrain(_requestedSize);
1066 }
1067
1068 @override
1069 void performLayout() {
1070 size = constraints.constrain(_requestedSize);
1071 if (child != null) {
1072 child!.layout(constraints, parentUsesSize: true);
1073 alignChild();
1074 }
1075 }
1076}
1077
1078/// Sizes its child to a fraction of the total available space.
1079///
1080/// For both its width and height, this render object imposes a tight
1081/// constraint on its child that is a multiple (typically less than 1.0) of the
1082/// maximum constraint it received from its parent on that axis. If the factor
1083/// for a given axis is null, then the constraints from the parent are just
1084/// passed through instead.
1085///
1086/// It then tries to size itself to the size of its child. Where this is not
1087/// possible (e.g. if the constraints from the parent are themselves tight), the
1088/// child is aligned according to [alignment].
1089class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
1090 /// Creates a render box that sizes its child to a fraction of the total available space.
1091 ///
1092 /// If non-null, the [widthFactor] and [heightFactor] arguments must be
1093 /// non-negative.
1094 ///
1095 /// The [textDirection] must be non-null if the [alignment] is
1096 /// direction-sensitive.
1097 RenderFractionallySizedOverflowBox({
1098 super.child,
1099 double? widthFactor,
1100 double? heightFactor,
1101 super.alignment,
1102 super.textDirection,
1103 }) : _widthFactor = widthFactor,
1104 _heightFactor = heightFactor {
1105 assert(_widthFactor == null || _widthFactor! >= 0.0);
1106 assert(_heightFactor == null || _heightFactor! >= 0.0);
1107 }
1108
1109 /// If non-null, the factor of the incoming width to use.
1110 ///
1111 /// If non-null, the child is given a tight width constraint that is the max
1112 /// incoming width constraint multiplied by this factor. If null, the child is
1113 /// given the incoming width constraints.
1114 double? get widthFactor => _widthFactor;
1115 double? _widthFactor;
1116 set widthFactor(double? value) {
1117 assert(value == null || value >= 0.0);
1118 if (_widthFactor == value) {
1119 return;
1120 }
1121 _widthFactor = value;
1122 markNeedsLayout();
1123 }
1124
1125 /// If non-null, the factor of the incoming height to use.
1126 ///
1127 /// If non-null, the child is given a tight height constraint that is the max
1128 /// incoming width constraint multiplied by this factor. If null, the child is
1129 /// given the incoming width constraints.
1130 double? get heightFactor => _heightFactor;
1131 double? _heightFactor;
1132 set heightFactor(double? value) {
1133 assert(value == null || value >= 0.0);
1134 if (_heightFactor == value) {
1135 return;
1136 }
1137 _heightFactor = value;
1138 markNeedsLayout();
1139 }
1140
1141 BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
1142 double minWidth = constraints.minWidth;
1143 double maxWidth = constraints.maxWidth;
1144 if (_widthFactor != null) {
1145 final double width = maxWidth * _widthFactor!;
1146 minWidth = width;
1147 maxWidth = width;
1148 }
1149 double minHeight = constraints.minHeight;
1150 double maxHeight = constraints.maxHeight;
1151 if (_heightFactor != null) {
1152 final double height = maxHeight * _heightFactor!;
1153 minHeight = height;
1154 maxHeight = height;
1155 }
1156 return BoxConstraints(
1157 minWidth: minWidth,
1158 maxWidth: maxWidth,
1159 minHeight: minHeight,
1160 maxHeight: maxHeight,
1161 );
1162 }
1163
1164 @override
1165 double computeMinIntrinsicWidth(double height) {
1166 final double result;
1167 if (child == null) {
1168 result = super.computeMinIntrinsicWidth(height);
1169 } else {
1170 // the following line relies on double.infinity absorption
1171 result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
1172 }
1173 assert(result.isFinite);
1174 return result / (_widthFactor ?? 1.0);
1175 }
1176
1177 @override
1178 double computeMaxIntrinsicWidth(double height) {
1179 final double result;
1180 if (child == null) {
1181 result = super.computeMaxIntrinsicWidth(height);
1182 } else {
1183 // the following line relies on double.infinity absorption
1184 result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
1185 }
1186 assert(result.isFinite);
1187 return result / (_widthFactor ?? 1.0);
1188 }
1189
1190 @override
1191 double computeMinIntrinsicHeight(double width) {
1192 final double result;
1193 if (child == null) {
1194 result = super.computeMinIntrinsicHeight(width);
1195 } else {
1196 // the following line relies on double.infinity absorption
1197 result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
1198 }
1199 assert(result.isFinite);
1200 return result / (_heightFactor ?? 1.0);
1201 }
1202
1203 @override
1204 double computeMaxIntrinsicHeight(double width) {
1205 final double result;
1206 if (child == null) {
1207 result = super.computeMaxIntrinsicHeight(width);
1208 } else {
1209 // the following line relies on double.infinity absorption
1210 result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
1211 }
1212 assert(result.isFinite);
1213 return result / (_heightFactor ?? 1.0);
1214 }
1215
1216 @override
1217 @protected
1218 Size computeDryLayout(covariant BoxConstraints constraints) {
1219 if (child != null) {
1220 final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
1221 return constraints.constrain(childSize);
1222 }
1223 return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
1224 }
1225
1226 @override
1227 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1228 final RenderBox? child = this.child;
1229 if (child == null) {
1230 return null;
1231 }
1232 final BoxConstraints childConstraints = _getInnerConstraints(constraints);
1233 final double? result = child.getDryBaseline(childConstraints, baseline);
1234 if (result == null) {
1235 return null;
1236 }
1237 final Size childSize = child.getDryLayout(childConstraints);
1238 final Size size = getDryLayout(constraints);
1239 return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy;
1240 }
1241
1242 @override
1243 void performLayout() {
1244 if (child != null) {
1245 child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
1246 size = constraints.constrain(child!.size);
1247 alignChild();
1248 } else {
1249 size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
1250 }
1251 }
1252
1253 @override
1254 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1255 super.debugFillProperties(properties);
1256 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
1257 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
1258 }
1259}
1260
1261/// A delegate for computing the layout of a render object with a single child.
1262///
1263/// Used by [CustomSingleChildLayout] (in the widgets library) and
1264/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
1265///
1266/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
1267/// its incoming constraints to determine its size. It then calls
1268/// [getConstraintsForChild] to determine the constraints to apply to the child.
1269/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
1270/// calls [getPositionForChild] to determine the child's position.
1271///
1272/// The [shouldRelayout] method is called when a new instance of the class
1273/// is provided, to check if the new instance actually represents different
1274/// information.
1275///
1276/// The most efficient way to trigger a relayout is to supply a `relayout`
1277/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
1278/// layout will listen to this value and relayout whenever the Listenable
1279/// notifies its listeners, such as when an [Animation] ticks. This allows
1280/// the custom layout to avoid the build phase of the pipeline.
1281///
1282/// See also:
1283///
1284/// * [CustomSingleChildLayout], the widget that uses this delegate.
1285/// * [RenderCustomSingleChildLayoutBox], render object that uses this
1286/// delegate.
1287abstract class SingleChildLayoutDelegate {
1288 /// Creates a layout delegate.
1289 ///
1290 /// The layout will update whenever [relayout] notifies its listeners.
1291 const SingleChildLayoutDelegate({Listenable? relayout}) : _relayout = relayout;
1292
1293 final Listenable? _relayout;
1294
1295 /// The size of this object given the incoming constraints.
1296 ///
1297 /// Defaults to the biggest size that satisfies the given constraints.
1298 Size getSize(BoxConstraints constraints) => constraints.biggest;
1299
1300 /// The constraints for the child given the incoming constraints.
1301 ///
1302 /// During layout, the child is given the layout constraints returned by this
1303 /// function. The child is required to pick a size for itself that satisfies
1304 /// these constraints.
1305 ///
1306 /// Defaults to the given constraints.
1307 BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
1308
1309 /// The position where the child should be placed.
1310 ///
1311 /// The `size` argument is the size of the parent, which might be different
1312 /// from the value returned by [getSize] if that size doesn't satisfy the
1313 /// constraints passed to [getSize]. The `childSize` argument is the size of
1314 /// the child, which will satisfy the constraints returned by
1315 /// [getConstraintsForChild].
1316 ///
1317 /// Defaults to positioning the child in the upper left corner of the parent.
1318 Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1319
1320 /// Called whenever a new instance of the custom layout delegate class is
1321 /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
1322 /// that a new [CustomSingleChildLayout] object is created with a new instance
1323 /// of the custom layout delegate class (which amounts to the same thing,
1324 /// because the latter is implemented in terms of the former).
1325 ///
1326 /// If the new instance represents different information than the old
1327 /// instance, then the method should return true, otherwise it should return
1328 /// false.
1329 ///
1330 /// If the method returns false, then the [getSize],
1331 /// [getConstraintsForChild], and [getPositionForChild] calls might be
1332 /// optimized away.
1333 ///
1334 /// It's possible that the layout methods will get called even if
1335 /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1336 /// It's also possible that the layout method will get called
1337 /// without [shouldRelayout] being called at all (e.g. if the parent changes
1338 /// size).
1339 bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
1340}
1341
1342/// Defers the layout of its single child to a delegate.
1343///
1344/// The delegate can determine the layout constraints for the child and can
1345/// decide where to position the child. The delegate can also determine the size
1346/// of the parent, but the size of the parent cannot depend on the size of the
1347/// child.
1348class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1349 /// Creates a render box that defers its layout to a delegate.
1350 ///
1351 /// The [delegate] argument must not be null.
1352 RenderCustomSingleChildLayoutBox({RenderBox? child, required SingleChildLayoutDelegate delegate})
1353 : _delegate = delegate,
1354 super(child);
1355
1356 /// A delegate that controls this object's layout.
1357 SingleChildLayoutDelegate get delegate => _delegate;
1358 SingleChildLayoutDelegate _delegate;
1359 set delegate(SingleChildLayoutDelegate newDelegate) {
1360 if (_delegate == newDelegate) {
1361 return;
1362 }
1363 final SingleChildLayoutDelegate oldDelegate = _delegate;
1364 if (newDelegate.runtimeType != oldDelegate.runtimeType ||
1365 newDelegate.shouldRelayout(oldDelegate)) {
1366 markNeedsLayout();
1367 }
1368 _delegate = newDelegate;
1369 if (attached) {
1370 oldDelegate._relayout?.removeListener(markNeedsLayout);
1371 newDelegate._relayout?.addListener(markNeedsLayout);
1372 }
1373 }
1374
1375 @override
1376 void attach(PipelineOwner owner) {
1377 super.attach(owner);
1378 _delegate._relayout?.addListener(markNeedsLayout);
1379 }
1380
1381 @override
1382 void detach() {
1383 _delegate._relayout?.removeListener(markNeedsLayout);
1384 super.detach();
1385 }
1386
1387 Size _getSize(BoxConstraints constraints) {
1388 return constraints.constrain(_delegate.getSize(constraints));
1389 }
1390
1391 // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
1392 // figure out the intrinsic dimensions. We really should either not support intrinsics,
1393 // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
1394
1395 @override
1396 double computeMinIntrinsicWidth(double height) {
1397 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1398 if (width.isFinite) {
1399 return width;
1400 }
1401 return 0.0;
1402 }
1403
1404 @override
1405 double computeMaxIntrinsicWidth(double height) {
1406 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1407 if (width.isFinite) {
1408 return width;
1409 }
1410 return 0.0;
1411 }
1412
1413 @override
1414 double computeMinIntrinsicHeight(double width) {
1415 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1416 if (height.isFinite) {
1417 return height;
1418 }
1419 return 0.0;
1420 }
1421
1422 @override
1423 double computeMaxIntrinsicHeight(double width) {
1424 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1425 if (height.isFinite) {
1426 return height;
1427 }
1428 return 0.0;
1429 }
1430
1431 @override
1432 @protected
1433 Size computeDryLayout(covariant BoxConstraints constraints) {
1434 return _getSize(constraints);
1435 }
1436
1437 @override
1438 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1439 final RenderBox? child = this.child;
1440 if (child == null) {
1441 return null;
1442 }
1443 final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1444 final double? result = child.getDryBaseline(childConstraints, baseline);
1445 if (result == null) {
1446 return null;
1447 }
1448 return result +
1449 delegate
1450 .getPositionForChild(
1451 _getSize(constraints),
1452 childConstraints.isTight
1453 ? childConstraints.smallest
1454 : child.getDryLayout(childConstraints),
1455 )
1456 .dy;
1457 }
1458
1459 @override
1460 void performLayout() {
1461 size = _getSize(constraints);
1462 if (child != null) {
1463 final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1464 assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1465 child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1466 final BoxParentData childParentData = child!.parentData! as BoxParentData;
1467 childParentData.offset = delegate.getPositionForChild(
1468 size,
1469 childConstraints.isTight ? childConstraints.smallest : child!.size,
1470 );
1471 }
1472 }
1473}
1474
1475/// Shifts the child down such that the child's baseline (or the
1476/// bottom of the child, if the child has no baseline) is [baseline]
1477/// logical pixels below the top of this box, then sizes this box to
1478/// contain the child.
1479///
1480/// If [baseline] is less than the distance from the top of the child
1481/// to the baseline of the child, then the child will overflow the top
1482/// of the box. This is typically not desirable, in particular, that
1483/// part of the child will not be found when doing hit tests, so the
1484/// user cannot interact with that part of the child.
1485///
1486/// This box will be sized so that its bottom is coincident with the
1487/// bottom of the child. This means if this box shifts the child down,
1488/// there will be space between the top of this box and the top of the
1489/// child, but there is never space between the bottom of the child
1490/// and the bottom of the box.
1491class RenderBaseline extends RenderShiftedBox {
1492 /// Creates a [RenderBaseline] object.
1493 RenderBaseline({RenderBox? child, required double baseline, required TextBaseline baselineType})
1494 : _baseline = baseline,
1495 _baselineType = baselineType,
1496 super(child);
1497
1498 /// The number of logical pixels from the top of this box at which to position
1499 /// the child's baseline.
1500 double get baseline => _baseline;
1501 double _baseline;
1502 set baseline(double value) {
1503 if (_baseline == value) {
1504 return;
1505 }
1506 _baseline = value;
1507 markNeedsLayout();
1508 }
1509
1510 /// The type of baseline to use for positioning the child.
1511 TextBaseline get baselineType => _baselineType;
1512 TextBaseline _baselineType;
1513 set baselineType(TextBaseline value) {
1514 if (_baselineType == value) {
1515 return;
1516 }
1517 _baselineType = value;
1518 markNeedsLayout();
1519 }
1520
1521 ({Size size, double top}) _computeSizes(
1522 covariant BoxConstraints constraints,
1523 ChildLayouter layoutChild,
1524 ChildBaselineGetter getBaseline,
1525 ) {
1526 final RenderBox? child = this.child;
1527 if (child == null) {
1528 return (size: constraints.smallest, top: 0);
1529 }
1530 final BoxConstraints childConstraints = constraints.loosen();
1531 final Size childSize = layoutChild(child, childConstraints);
1532 final double childBaseline =
1533 getBaseline(child, childConstraints, baselineType) ?? childSize.height;
1534 final double top = baseline - childBaseline;
1535 return (size: constraints.constrain(Size(childSize.width, top + childSize.height)), top: top);
1536 }
1537
1538 @override
1539 @protected
1540 Size computeDryLayout(covariant BoxConstraints constraints) {
1541 return _computeSizes(
1542 constraints,
1543 ChildLayoutHelper.dryLayoutChild,
1544 ChildLayoutHelper.getDryBaseline,
1545 ).size;
1546 }
1547
1548 @override
1549 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
1550 final RenderBox? child = this.child;
1551 final double? result1 = child?.getDryBaseline(constraints.loosen(), baseline);
1552 final double? result2 = child?.getDryBaseline(constraints.loosen(), baselineType);
1553 if (result1 == null || result2 == null) {
1554 return null;
1555 }
1556 return this.baseline + result1 - result2;
1557 }
1558
1559 @override
1560 void performLayout() {
1561 final (:Size size, :double top) = _computeSizes(
1562 constraints,
1563 ChildLayoutHelper.layoutChild,
1564 ChildLayoutHelper.getBaseline,
1565 );
1566 this.size = size;
1567 (child?.parentData as BoxParentData?)?.offset = Offset(0.0, top);
1568 }
1569
1570 @override
1571 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1572 super.debugFillProperties(properties);
1573 properties.add(DoubleProperty('baseline', baseline));
1574 properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1575 }
1576}
1577

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com