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'; |
8 | library; |
9 | |
10 | import 'dart:math' as math; |
11 | |
12 | import 'package:flutter/foundation.dart'; |
13 | |
14 | import 'box.dart'; |
15 | import 'debug.dart'; |
16 | import 'debug_overflow_indicator.dart'; |
17 | import 'layer.dart'; |
18 | import 'layout_helper.dart'; |
19 | import 'object.dart'; |
20 | import '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]. |
28 | typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints constraints); |
29 | |
30 | /// Abstract class for one-child-layout render boxes that provide control over |
31 | /// the child's position. |
32 | abstract 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. |
109 | class 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. |
275 | abstract 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. |
378 | class 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. |
548 | enum 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. |
590 | class 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. |
790 | class 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. |
998 | class 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]. |
1089 | class 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. |
1287 | abstract 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. |
1348 | class 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. |
1491 | class 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 |
Definitions
- RenderShiftedBox
- RenderShiftedBox
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDistanceToActualBaseline
- paint
- hitTestChildren
- RenderPadding
- RenderPadding
- _resolvedPadding
- _markNeedResolution
- padding
- padding
- textDirection
- textDirection
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDryLayout
- computeDryBaseline
- performLayout
- debugPaintSize
- debugFillProperties
- RenderAligningShiftedBox
- RenderAligningShiftedBox
- resolvedAlignment
- _markNeedResolution
- alignment
- alignment
- textDirection
- textDirection
- alignChild
- debugFillProperties
- RenderPositionedBox
- RenderPositionedBox
- widthFactor
- widthFactor
- heightFactor
- heightFactor
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDryLayout
- performLayout
- debugPaintSize
- debugFillProperties
- OverflowBoxFit
- RenderConstrainedOverflowBox
- RenderConstrainedOverflowBox
- minWidth
- minWidth
- maxWidth
- maxWidth
- minHeight
- minHeight
- maxHeight
- maxHeight
- fit
- fit
- _getInnerConstraints
- sizedByParent
- computeDryLayout
- computeDryBaseline
- performLayout
- debugFillProperties
- RenderConstraintsTransformBox
- RenderConstraintsTransformBox
- constraintsTransform
- constraintsTransform
- clipBehavior
- clipBehavior
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeDryLayout
- computeDryBaseline
- performLayout
- paint
- dispose
- describeApproximatePaintClip
- toStringShort
- RenderSizedOverflowBox
- RenderSizedOverflowBox
- requestedSize
- requestedSize
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDistanceToActualBaseline
- computeDryBaseline
- computeDryLayout
- performLayout
- RenderFractionallySizedOverflowBox
- RenderFractionallySizedOverflowBox
- widthFactor
- widthFactor
- heightFactor
- heightFactor
- _getInnerConstraints
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDryLayout
- computeDryBaseline
- performLayout
- debugFillProperties
- SingleChildLayoutDelegate
- SingleChildLayoutDelegate
- getSize
- getConstraintsForChild
- getPositionForChild
- shouldRelayout
- RenderCustomSingleChildLayoutBox
- RenderCustomSingleChildLayoutBox
- delegate
- delegate
- attach
- detach
- _getSize
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- computeDryLayout
- computeDryBaseline
- performLayout
- RenderBaseline
- RenderBaseline
- baseline
- baseline
- baselineType
- baselineType
- _computeSizes
- computeDryLayout
- computeDryBaseline
- performLayout
Learn more about Flutter for embedded and desktop on industrialflutter.com