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 | import 'dart:math' as math; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | |
9 | import 'box.dart'; |
10 | import 'debug_overflow_indicator.dart'; |
11 | import 'layer.dart'; |
12 | import 'layout_helper.dart'; |
13 | import 'object.dart'; |
14 | |
15 | /// How the child is inscribed into the available space. |
16 | /// |
17 | /// See also: |
18 | /// |
19 | /// * [RenderFlex], the flex render object. |
20 | /// * [Column], [Row], and [Flex], the flex widgets. |
21 | /// * [Expanded], the widget equivalent of [tight]. |
22 | /// * [Flexible], the widget equivalent of [loose]. |
23 | enum FlexFit { |
24 | /// The child is forced to fill the available space. |
25 | /// |
26 | /// The [Expanded] widget assigns this kind of [FlexFit] to its child. |
27 | tight, |
28 | |
29 | /// The child can be at most as large as the available space (but is |
30 | /// allowed to be smaller). |
31 | /// |
32 | /// The [Flexible] widget assigns this kind of [FlexFit] to its child. |
33 | loose, |
34 | } |
35 | |
36 | /// Parent data for use with [RenderFlex]. |
37 | class FlexParentData extends ContainerBoxParentData<RenderBox> { |
38 | /// The flex factor to use for this child. |
39 | /// |
40 | /// If null or zero, the child is inflexible and determines its own size. If |
41 | /// non-zero, the amount of space the child's can occupy in the main axis is |
42 | /// determined by dividing the free space (after placing the inflexible |
43 | /// children) according to the flex factors of the flexible children. |
44 | int? flex; |
45 | |
46 | /// How a flexible child is inscribed into the available space. |
47 | /// |
48 | /// If [flex] is non-zero, the [fit] determines whether the child fills the |
49 | /// space the parent makes available during layout. If the fit is |
50 | /// [FlexFit.tight], the child is required to fill the available space. If the |
51 | /// fit is [FlexFit.loose], the child can be at most as large as the available |
52 | /// space (but is allowed to be smaller). |
53 | FlexFit? fit; |
54 | |
55 | @override |
56 | String toString() => ' ${super.toString()}; flex= $flex; fit= $fit' ; |
57 | } |
58 | |
59 | /// How much space should be occupied in the main axis. |
60 | /// |
61 | /// During a flex layout, available space along the main axis is allocated to |
62 | /// children. After allocating space, there might be some remaining free space. |
63 | /// This value controls whether to maximize or minimize the amount of free |
64 | /// space, subject to the incoming layout constraints. |
65 | /// |
66 | /// See also: |
67 | /// |
68 | /// * [Column], [Row], and [Flex], the flex widgets. |
69 | /// * [Expanded] and [Flexible], the widgets that controls a flex widgets' |
70 | /// children's flex. |
71 | /// * [RenderFlex], the flex render object. |
72 | /// * [MainAxisAlignment], which controls how the free space is distributed. |
73 | enum MainAxisSize { |
74 | /// Minimize the amount of free space along the main axis, subject to the |
75 | /// incoming layout constraints. |
76 | /// |
77 | /// If the incoming layout constraints have a large enough |
78 | /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still |
79 | /// be a non-zero amount of free space. |
80 | /// |
81 | /// If the incoming layout constraints are unbounded, and any children have a |
82 | /// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by |
83 | /// [Expanded]), the [RenderFlex] will assert, because there would be infinite |
84 | /// remaining free space and boxes cannot be given infinite size. |
85 | min, |
86 | |
87 | /// Maximize the amount of free space along the main axis, subject to the |
88 | /// incoming layout constraints. |
89 | /// |
90 | /// If the incoming layout constraints have a small enough |
91 | /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still |
92 | /// be no free space. |
93 | /// |
94 | /// If the incoming layout constraints are unbounded, the [RenderFlex] will |
95 | /// assert, because there would be infinite remaining free space and boxes |
96 | /// cannot be given infinite size. |
97 | max, |
98 | } |
99 | |
100 | /// How the children should be placed along the main axis in a flex layout. |
101 | /// |
102 | /// See also: |
103 | /// |
104 | /// * [Column], [Row], and [Flex], the flex widgets. |
105 | /// * [RenderFlex], the flex render object. |
106 | enum MainAxisAlignment { |
107 | /// Place the children as close to the start of the main axis as possible. |
108 | /// |
109 | /// If this value is used in a horizontal direction, a [TextDirection] must be |
110 | /// available to determine if the start is the left or the right. |
111 | /// |
112 | /// If this value is used in a vertical direction, a [VerticalDirection] must be |
113 | /// available to determine if the start is the top or the bottom. |
114 | start, |
115 | |
116 | /// Place the children as close to the end of the main axis as possible. |
117 | /// |
118 | /// If this value is used in a horizontal direction, a [TextDirection] must be |
119 | /// available to determine if the end is the left or the right. |
120 | /// |
121 | /// If this value is used in a vertical direction, a [VerticalDirection] must be |
122 | /// available to determine if the end is the top or the bottom. |
123 | end, |
124 | |
125 | /// Place the children as close to the middle of the main axis as possible. |
126 | center, |
127 | |
128 | /// Place the free space evenly between the children. |
129 | spaceBetween, |
130 | |
131 | /// Place the free space evenly between the children as well as half of that |
132 | /// space before and after the first and last child. |
133 | spaceAround, |
134 | |
135 | /// Place the free space evenly between the children as well as before and |
136 | /// after the first and last child. |
137 | spaceEvenly, |
138 | } |
139 | |
140 | /// How the children should be placed along the cross axis in a flex layout. |
141 | /// |
142 | /// See also: |
143 | /// |
144 | /// * [Column], [Row], and [Flex], the flex widgets. |
145 | /// * [RenderFlex], the flex render object. |
146 | enum CrossAxisAlignment { |
147 | /// Place the children with their start edge aligned with the start side of |
148 | /// the cross axis. |
149 | /// |
150 | /// For example, in a column (a flex with a vertical axis) whose |
151 | /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the |
152 | /// children along the left edge of the column. |
153 | /// |
154 | /// If this value is used in a horizontal direction, a [TextDirection] must be |
155 | /// available to determine if the start is the left or the right. |
156 | /// |
157 | /// If this value is used in a vertical direction, a [VerticalDirection] must be |
158 | /// available to determine if the start is the top or the bottom. |
159 | start, |
160 | |
161 | /// Place the children as close to the end of the cross axis as possible. |
162 | /// |
163 | /// For example, in a column (a flex with a vertical axis) whose |
164 | /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the |
165 | /// children along the right edge of the column. |
166 | /// |
167 | /// If this value is used in a horizontal direction, a [TextDirection] must be |
168 | /// available to determine if the end is the left or the right. |
169 | /// |
170 | /// If this value is used in a vertical direction, a [VerticalDirection] must be |
171 | /// available to determine if the end is the top or the bottom. |
172 | end, |
173 | |
174 | /// Place the children so that their centers align with the middle of the |
175 | /// cross axis. |
176 | /// |
177 | /// This is the default cross-axis alignment. |
178 | center, |
179 | |
180 | /// Require the children to fill the cross axis. |
181 | /// |
182 | /// This causes the constraints passed to the children to be tight in the |
183 | /// cross axis. |
184 | stretch, |
185 | |
186 | /// Place the children along the cross axis such that their baselines match. |
187 | /// |
188 | /// Because baselines are always horizontal, this alignment is intended for |
189 | /// horizontal main axes. If the main axis is vertical, then this value is |
190 | /// treated like [start]. |
191 | /// |
192 | /// For horizontal main axes, if the minimum height constraint passed to the |
193 | /// flex layout exceeds the intrinsic height of the cross axis, children will |
194 | /// be aligned as close to the top as they can be while honoring the baseline |
195 | /// alignment. In other words, the extra space will be below all the children. |
196 | /// |
197 | /// Children who report no baseline will be top-aligned. |
198 | baseline, |
199 | } |
200 | |
201 | bool? _startIsTopLeft(Axis direction, TextDirection? textDirection, VerticalDirection? verticalDirection) { |
202 | // If the relevant value of textDirection or verticalDirection is null, this returns null too. |
203 | switch (direction) { |
204 | case Axis.horizontal: |
205 | switch (textDirection) { |
206 | case TextDirection.ltr: |
207 | return true; |
208 | case TextDirection.rtl: |
209 | return false; |
210 | case null: |
211 | return null; |
212 | } |
213 | case Axis.vertical: |
214 | switch (verticalDirection) { |
215 | case VerticalDirection.down: |
216 | return true; |
217 | case VerticalDirection.up: |
218 | return false; |
219 | case null: |
220 | return null; |
221 | } |
222 | } |
223 | } |
224 | |
225 | typedef _ChildSizingFunction = double Function(RenderBox child, double extent); |
226 | |
227 | /// Displays its children in a one-dimensional array. |
228 | /// |
229 | /// ## Layout algorithm |
230 | /// |
231 | /// _This section describes how the framework causes [RenderFlex] to position |
232 | /// its children._ |
233 | /// _See [BoxConstraints] for an introduction to box layout models._ |
234 | /// |
235 | /// Layout for a [RenderFlex] proceeds in six steps: |
236 | /// |
237 | /// 1. Layout each child with a null or zero flex factor with unbounded main |
238 | /// axis constraints and the incoming cross axis constraints. If the |
239 | /// [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight |
240 | /// cross axis constraints that match the incoming max extent in the cross |
241 | /// axis. |
242 | /// 2. Divide the remaining main axis space among the children with non-zero |
243 | /// flex factors according to their flex factor. For example, a child with a |
244 | /// flex factor of 2.0 will receive twice the amount of main axis space as a |
245 | /// child with a flex factor of 1.0. |
246 | /// 3. Layout each of the remaining children with the same cross axis |
247 | /// constraints as in step 1, but instead of using unbounded main axis |
248 | /// constraints, use max axis constraints based on the amount of space |
249 | /// allocated in step 2. Children with [Flexible.fit] properties that are |
250 | /// [FlexFit.tight] are given tight constraints (i.e., forced to fill the |
251 | /// allocated space), and children with [Flexible.fit] properties that are |
252 | /// [FlexFit.loose] are given loose constraints (i.e., not forced to fill the |
253 | /// allocated space). |
254 | /// 4. The cross axis extent of the [RenderFlex] is the maximum cross axis |
255 | /// extent of the children (which will always satisfy the incoming |
256 | /// constraints). |
257 | /// 5. The main axis extent of the [RenderFlex] is determined by the |
258 | /// [mainAxisSize] property. If the [mainAxisSize] property is |
259 | /// [MainAxisSize.max], then the main axis extent of the [RenderFlex] is the |
260 | /// max extent of the incoming main axis constraints. If the [mainAxisSize] |
261 | /// property is [MainAxisSize.min], then the main axis extent of the [Flex] |
262 | /// is the sum of the main axis extents of the children (subject to the |
263 | /// incoming constraints). |
264 | /// 6. Determine the position for each child according to the |
265 | /// [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the |
266 | /// [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis |
267 | /// space that has not been allocated to children is divided evenly and |
268 | /// placed between the children. |
269 | /// |
270 | /// See also: |
271 | /// |
272 | /// * [Flex], the widget equivalent. |
273 | /// * [Row] and [Column], direction-specific variants of [Flex]. |
274 | class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>, |
275 | RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>, |
276 | DebugOverflowIndicatorMixin { |
277 | /// Creates a flex render object. |
278 | /// |
279 | /// By default, the flex layout is horizontal and children are aligned to the |
280 | /// start of the main axis and the center of the cross axis. |
281 | RenderFlex({ |
282 | List<RenderBox>? children, |
283 | Axis direction = Axis.horizontal, |
284 | MainAxisSize mainAxisSize = MainAxisSize.max, |
285 | MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, |
286 | CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, |
287 | TextDirection? textDirection, |
288 | VerticalDirection verticalDirection = VerticalDirection.down, |
289 | TextBaseline? textBaseline, |
290 | Clip clipBehavior = Clip.none, |
291 | }) : _direction = direction, |
292 | _mainAxisAlignment = mainAxisAlignment, |
293 | _mainAxisSize = mainAxisSize, |
294 | _crossAxisAlignment = crossAxisAlignment, |
295 | _textDirection = textDirection, |
296 | _verticalDirection = verticalDirection, |
297 | _textBaseline = textBaseline, |
298 | _clipBehavior = clipBehavior { |
299 | addAll(children); |
300 | } |
301 | |
302 | /// The direction to use as the main axis. |
303 | Axis get direction => _direction; |
304 | Axis _direction; |
305 | set direction(Axis value) { |
306 | if (_direction != value) { |
307 | _direction = value; |
308 | markNeedsLayout(); |
309 | } |
310 | } |
311 | |
312 | /// How the children should be placed along the main axis. |
313 | /// |
314 | /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is |
315 | /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the |
316 | /// [textDirection] must not be null. |
317 | /// |
318 | /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is |
319 | /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the |
320 | /// [verticalDirection] must not be null. |
321 | MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment; |
322 | MainAxisAlignment _mainAxisAlignment; |
323 | set mainAxisAlignment(MainAxisAlignment value) { |
324 | if (_mainAxisAlignment != value) { |
325 | _mainAxisAlignment = value; |
326 | markNeedsLayout(); |
327 | } |
328 | } |
329 | |
330 | /// How much space should be occupied in the main axis. |
331 | /// |
332 | /// After allocating space to children, there might be some remaining free |
333 | /// space. This value controls whether to maximize or minimize the amount of |
334 | /// free space, subject to the incoming layout constraints. |
335 | /// |
336 | /// If some children have a non-zero flex factors (and none have a fit of |
337 | /// [FlexFit.loose]), they will expand to consume all the available space and |
338 | /// there will be no remaining free space to maximize or minimize, making this |
339 | /// value irrelevant to the final layout. |
340 | MainAxisSize get mainAxisSize => _mainAxisSize; |
341 | MainAxisSize _mainAxisSize; |
342 | set mainAxisSize(MainAxisSize value) { |
343 | if (_mainAxisSize != value) { |
344 | _mainAxisSize = value; |
345 | markNeedsLayout(); |
346 | } |
347 | } |
348 | |
349 | /// How the children should be placed along the cross axis. |
350 | /// |
351 | /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is |
352 | /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the |
353 | /// [verticalDirection] must not be null. |
354 | /// |
355 | /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is |
356 | /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the |
357 | /// [textDirection] must not be null. |
358 | CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment; |
359 | CrossAxisAlignment _crossAxisAlignment; |
360 | set crossAxisAlignment(CrossAxisAlignment value) { |
361 | if (_crossAxisAlignment != value) { |
362 | _crossAxisAlignment = value; |
363 | markNeedsLayout(); |
364 | } |
365 | } |
366 | |
367 | /// Determines the order to lay children out horizontally and how to interpret |
368 | /// `start` and `end` in the horizontal direction. |
369 | /// |
370 | /// If the [direction] is [Axis.horizontal], this controls the order in which |
371 | /// children are positioned (left-to-right or right-to-left), and the meaning |
372 | /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and |
373 | /// [MainAxisAlignment.end] values. |
374 | /// |
375 | /// If the [direction] is [Axis.horizontal], and either the |
376 | /// [mainAxisAlignment] is either [MainAxisAlignment.start] or |
377 | /// [MainAxisAlignment.end], or there's more than one child, then the |
378 | /// [textDirection] must not be null. |
379 | /// |
380 | /// If the [direction] is [Axis.vertical], this controls the meaning of the |
381 | /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and |
382 | /// [CrossAxisAlignment.end] values. |
383 | /// |
384 | /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is |
385 | /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the |
386 | /// [textDirection] must not be null. |
387 | TextDirection? get textDirection => _textDirection; |
388 | TextDirection? _textDirection; |
389 | set textDirection(TextDirection? value) { |
390 | if (_textDirection != value) { |
391 | _textDirection = value; |
392 | markNeedsLayout(); |
393 | } |
394 | } |
395 | |
396 | /// Determines the order to lay children out vertically and how to interpret |
397 | /// `start` and `end` in the vertical direction. |
398 | /// |
399 | /// If the [direction] is [Axis.vertical], this controls which order children |
400 | /// are painted in (down or up), the meaning of the [mainAxisAlignment] |
401 | /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values. |
402 | /// |
403 | /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment] |
404 | /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's |
405 | /// more than one child, then the [verticalDirection] must not be null. |
406 | /// |
407 | /// If the [direction] is [Axis.horizontal], this controls the meaning of the |
408 | /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and |
409 | /// [CrossAxisAlignment.end] values. |
410 | /// |
411 | /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is |
412 | /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the |
413 | /// [verticalDirection] must not be null. |
414 | VerticalDirection get verticalDirection => _verticalDirection; |
415 | VerticalDirection _verticalDirection; |
416 | set verticalDirection(VerticalDirection value) { |
417 | if (_verticalDirection != value) { |
418 | _verticalDirection = value; |
419 | markNeedsLayout(); |
420 | } |
421 | } |
422 | |
423 | /// If aligning items according to their baseline, which baseline to use. |
424 | /// |
425 | /// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline]. |
426 | TextBaseline? get textBaseline => _textBaseline; |
427 | TextBaseline? _textBaseline; |
428 | set textBaseline(TextBaseline? value) { |
429 | assert(_crossAxisAlignment != CrossAxisAlignment.baseline || value != null); |
430 | if (_textBaseline != value) { |
431 | _textBaseline = value; |
432 | markNeedsLayout(); |
433 | } |
434 | } |
435 | |
436 | bool get _debugHasNecessaryDirections { |
437 | if (firstChild != null && lastChild != firstChild) { |
438 | // i.e. there's more than one child |
439 | switch (direction) { |
440 | case Axis.horizontal: |
441 | assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.' ); |
442 | case Axis.vertical: |
443 | break; |
444 | } |
445 | } |
446 | if (mainAxisAlignment == MainAxisAlignment.start || |
447 | mainAxisAlignment == MainAxisAlignment.end) { |
448 | switch (direction) { |
449 | case Axis.horizontal: |
450 | assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.' ); |
451 | case Axis.vertical: |
452 | break; |
453 | } |
454 | } |
455 | if (crossAxisAlignment == CrossAxisAlignment.start || |
456 | crossAxisAlignment == CrossAxisAlignment.end) { |
457 | switch (direction) { |
458 | case Axis.horizontal: |
459 | break; |
460 | case Axis.vertical: |
461 | assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.' ); |
462 | } |
463 | } |
464 | return true; |
465 | } |
466 | |
467 | // Set during layout if overflow occurred on the main axis. |
468 | double _overflow = 0; |
469 | // Check whether any meaningful overflow is present. Values below an epsilon |
470 | // are treated as not overflowing. |
471 | bool get _hasOverflow => _overflow > precisionErrorTolerance; |
472 | |
473 | /// {@macro flutter.material.Material.clipBehavior} |
474 | /// |
475 | /// Defaults to [Clip.none]. |
476 | Clip get clipBehavior => _clipBehavior; |
477 | Clip _clipBehavior = Clip.none; |
478 | set clipBehavior(Clip value) { |
479 | if (value != _clipBehavior) { |
480 | _clipBehavior = value; |
481 | markNeedsPaint(); |
482 | markNeedsSemanticsUpdate(); |
483 | } |
484 | } |
485 | |
486 | @override |
487 | void setupParentData(RenderBox child) { |
488 | if (child.parentData is! FlexParentData) { |
489 | child.parentData = FlexParentData(); |
490 | } |
491 | } |
492 | |
493 | bool get _canComputeIntrinsics => crossAxisAlignment != CrossAxisAlignment.baseline; |
494 | |
495 | double _getIntrinsicSize({ |
496 | required Axis sizingDirection, |
497 | required double extent, // the extent in the direction that isn't the sizing direction |
498 | required _ChildSizingFunction childSize, // a method to find the size in the sizing direction |
499 | }) { |
500 | if (!_canComputeIntrinsics) { |
501 | // Intrinsics cannot be calculated without a full layout for |
502 | // baseline alignment. Throw an assertion and return 0.0 as documented |
503 | // on [RenderBox.computeMinIntrinsicWidth]. |
504 | assert( |
505 | RenderObject.debugCheckingIntrinsics, |
506 | 'Intrinsics are not available for CrossAxisAlignment.baseline.' , |
507 | ); |
508 | return 0.0; |
509 | } |
510 | if (_direction == sizingDirection) { |
511 | // INTRINSIC MAIN SIZE |
512 | // Intrinsic main size is the smallest size the flex container can take |
513 | // while maintaining the min/max-content contributions of its flex items. |
514 | double totalFlex = 0.0; |
515 | double inflexibleSpace = 0.0; |
516 | double maxFlexFractionSoFar = 0.0; |
517 | RenderBox? child = firstChild; |
518 | while (child != null) { |
519 | final int flex = _getFlex(child); |
520 | totalFlex += flex; |
521 | if (flex > 0) { |
522 | final double flexFraction = childSize(child, extent) / _getFlex(child); |
523 | maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction); |
524 | } else { |
525 | inflexibleSpace += childSize(child, extent); |
526 | } |
527 | final FlexParentData childParentData = child.parentData! as FlexParentData; |
528 | child = childParentData.nextSibling; |
529 | } |
530 | return maxFlexFractionSoFar * totalFlex + inflexibleSpace; |
531 | } else { |
532 | // INTRINSIC CROSS SIZE |
533 | // Intrinsic cross size is the max of the intrinsic cross sizes of the |
534 | // children, after the flexible children are fit into the available space, |
535 | // with the children sized using their max intrinsic dimensions. |
536 | |
537 | // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction. |
538 | final double availableMainSpace = extent; |
539 | int totalFlex = 0; |
540 | double inflexibleSpace = 0.0; |
541 | double maxCrossSize = 0.0; |
542 | RenderBox? child = firstChild; |
543 | while (child != null) { |
544 | final int flex = _getFlex(child); |
545 | totalFlex += flex; |
546 | late final double mainSize; |
547 | late final double crossSize; |
548 | if (flex == 0) { |
549 | switch (_direction) { |
550 | case Axis.horizontal: |
551 | mainSize = child.getMaxIntrinsicWidth(double.infinity); |
552 | crossSize = childSize(child, mainSize); |
553 | case Axis.vertical: |
554 | mainSize = child.getMaxIntrinsicHeight(double.infinity); |
555 | crossSize = childSize(child, mainSize); |
556 | } |
557 | inflexibleSpace += mainSize; |
558 | maxCrossSize = math.max(maxCrossSize, crossSize); |
559 | } |
560 | final FlexParentData childParentData = child.parentData! as FlexParentData; |
561 | child = childParentData.nextSibling; |
562 | } |
563 | |
564 | // Determine the spacePerFlex by allocating the remaining available space. |
565 | // When you're overconstrained spacePerFlex can be negative. |
566 | final double spacePerFlex = math.max(0.0, (availableMainSpace - inflexibleSpace) / totalFlex); |
567 | |
568 | // Size remaining (flexible) items, find the maximum cross size. |
569 | child = firstChild; |
570 | while (child != null) { |
571 | final int flex = _getFlex(child); |
572 | if (flex > 0) { |
573 | maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex)); |
574 | } |
575 | final FlexParentData childParentData = child.parentData! as FlexParentData; |
576 | child = childParentData.nextSibling; |
577 | } |
578 | |
579 | return maxCrossSize; |
580 | } |
581 | } |
582 | |
583 | @override |
584 | double computeMinIntrinsicWidth(double height) { |
585 | return _getIntrinsicSize( |
586 | sizingDirection: Axis.horizontal, |
587 | extent: height, |
588 | childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent), |
589 | ); |
590 | } |
591 | |
592 | @override |
593 | double computeMaxIntrinsicWidth(double height) { |
594 | return _getIntrinsicSize( |
595 | sizingDirection: Axis.horizontal, |
596 | extent: height, |
597 | childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent), |
598 | ); |
599 | } |
600 | |
601 | @override |
602 | double computeMinIntrinsicHeight(double width) { |
603 | return _getIntrinsicSize( |
604 | sizingDirection: Axis.vertical, |
605 | extent: width, |
606 | childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent), |
607 | ); |
608 | } |
609 | |
610 | @override |
611 | double computeMaxIntrinsicHeight(double width) { |
612 | return _getIntrinsicSize( |
613 | sizingDirection: Axis.vertical, |
614 | extent: width, |
615 | childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent), |
616 | ); |
617 | } |
618 | |
619 | @override |
620 | double? computeDistanceToActualBaseline(TextBaseline baseline) { |
621 | if (_direction == Axis.horizontal) { |
622 | return defaultComputeDistanceToHighestActualBaseline(baseline); |
623 | } |
624 | return defaultComputeDistanceToFirstActualBaseline(baseline); |
625 | } |
626 | |
627 | int _getFlex(RenderBox child) { |
628 | final FlexParentData childParentData = child.parentData! as FlexParentData; |
629 | return childParentData.flex ?? 0; |
630 | } |
631 | |
632 | FlexFit _getFit(RenderBox child) { |
633 | final FlexParentData childParentData = child.parentData! as FlexParentData; |
634 | return childParentData.fit ?? FlexFit.tight; |
635 | } |
636 | |
637 | double _getCrossSize(Size size) { |
638 | switch (_direction) { |
639 | case Axis.horizontal: |
640 | return size.height; |
641 | case Axis.vertical: |
642 | return size.width; |
643 | } |
644 | } |
645 | |
646 | double _getMainSize(Size size) { |
647 | switch (_direction) { |
648 | case Axis.horizontal: |
649 | return size.width; |
650 | case Axis.vertical: |
651 | return size.height; |
652 | } |
653 | } |
654 | |
655 | @override |
656 | @protected |
657 | Size computeDryLayout(covariant BoxConstraints constraints) { |
658 | if (!_canComputeIntrinsics) { |
659 | assert(debugCannotComputeDryLayout( |
660 | reason: 'Dry layout cannot be computed for CrossAxisAlignment.baseline, which requires a full layout.' , |
661 | )); |
662 | return Size.zero; |
663 | } |
664 | FlutterError? constraintsError; |
665 | assert(() { |
666 | constraintsError = _debugCheckConstraints( |
667 | constraints: constraints, |
668 | reportParentConstraints: false, |
669 | ); |
670 | return true; |
671 | }()); |
672 | if (constraintsError != null) { |
673 | assert(debugCannotComputeDryLayout(error: constraintsError)); |
674 | return Size.zero; |
675 | } |
676 | |
677 | final _LayoutSizes sizes = _computeSizes( |
678 | layoutChild: ChildLayoutHelper.dryLayoutChild, |
679 | constraints: constraints, |
680 | ); |
681 | |
682 | switch (_direction) { |
683 | case Axis.horizontal: |
684 | return constraints.constrain(Size(sizes.mainSize, sizes.crossSize)); |
685 | case Axis.vertical: |
686 | return constraints.constrain(Size(sizes.crossSize, sizes.mainSize)); |
687 | } |
688 | } |
689 | |
690 | FlutterError? _debugCheckConstraints({required BoxConstraints constraints, required bool reportParentConstraints}) { |
691 | FlutterError? result; |
692 | assert(() { |
693 | final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight; |
694 | final bool canFlex = maxMainSize < double.infinity; |
695 | RenderBox? child = firstChild; |
696 | while (child != null) { |
697 | final int flex = _getFlex(child); |
698 | if (flex > 0) { |
699 | final String identity = _direction == Axis.horizontal ? 'row' : 'column' ; |
700 | final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical' ; |
701 | final String dimension = _direction == Axis.horizontal ? 'width' : 'height' ; |
702 | DiagnosticsNode error, message; |
703 | final List<DiagnosticsNode> addendum = <DiagnosticsNode>[]; |
704 | if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) { |
705 | error = ErrorSummary('RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.' ); |
706 | message = ErrorDescription( |
707 | 'When a $identity is in a parent that does not provide a finite $dimension constraint, for example ' |
708 | 'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis ' |
709 | 'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to ' |
710 | 'expand to fill the remaining space in the $axis direction.' , |
711 | ); |
712 | if (reportParentConstraints) { // Constraints of parents are unavailable in dry layout. |
713 | RenderBox? node = this; |
714 | switch (_direction) { |
715 | case Axis.horizontal: |
716 | while (!node!.constraints.hasBoundedWidth && node.parent is RenderBox) { |
717 | node = node.parent! as RenderBox; |
718 | } |
719 | if (!node.constraints.hasBoundedWidth) { |
720 | node = null; |
721 | } |
722 | case Axis.vertical: |
723 | while (!node!.constraints.hasBoundedHeight && node.parent is RenderBox) { |
724 | node = node.parent! as RenderBox; |
725 | } |
726 | if (!node.constraints.hasBoundedHeight) { |
727 | node = null; |
728 | } |
729 | } |
730 | if (node != null) { |
731 | addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is' )); |
732 | } |
733 | } |
734 | addendum.add(ErrorHint('See also: https://flutter.dev/unbounded-constraints')); |
735 | } else { |
736 | return true; |
737 | } |
738 | result = FlutterError.fromParts(<DiagnosticsNode>[ |
739 | error, |
740 | message, |
741 | ErrorDescription( |
742 | 'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child ' |
743 | 'cannot simultaneously expand to fit its parent.' , |
744 | ), |
745 | ErrorHint( |
746 | 'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible ' |
747 | 'children (using Flexible rather than Expanded). This will allow the flexible children ' |
748 | 'to size themselves to less than the infinite remaining space they would otherwise be ' |
749 | 'forced to take, and then will cause the RenderFlex to shrink-wrap the children ' |
750 | 'rather than expanding to fit the maximum constraints provided by the parent.' , |
751 | ), |
752 | ErrorDescription( |
753 | 'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n' |
754 | ' https://flutter.dev/debugging/#rendering-layer\n' |
755 | ' http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html', |
756 | ),
|
757 | describeForError('The affected RenderFlex is' , style: DiagnosticsTreeStyle.errorProperty),
|
758 | DiagnosticsProperty<dynamic>('The creator information is set to' , debugCreator, style: DiagnosticsTreeStyle.errorProperty),
|
759 | ...addendum,
|
760 | ErrorDescription(
|
761 | "If none of the above helps enough to fix this problem, please don't hesitate to file a bug:\n"
|
762 | ' https://github.com/flutter/flutter/issues/new?template=2_bug.yml',
|
763 | ),
|
764 | ]);
|
765 | return true;
|
766 | }
|
767 | child = childAfter(child);
|
768 | }
|
769 | return true;
|
770 | }());
|
771 | return result;
|
772 | }
|
773 |
|
774 | _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
|
775 | assert(_debugHasNecessaryDirections);
|
776 |
|
777 | // Determine used flex factor, size inflexible items, calculate free space.
|
778 | int totalFlex = 0;
|
779 | final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
|
780 | final bool canFlex = maxMainSize < double.infinity;
|
781 |
|
782 | double crossSize = 0.0;
|
783 | double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
|
784 | RenderBox? child = firstChild;
|
785 | RenderBox? lastFlexChild;
|
786 | while (child != null) {
|
787 | final FlexParentData childParentData = child.parentData! as FlexParentData;
|
788 | final int flex = _getFlex(child);
|
789 | if (flex > 0) {
|
790 | totalFlex += flex;
|
791 | lastFlexChild = child;
|
792 | } else {
|
793 | final BoxConstraints innerConstraints;
|
794 | if (crossAxisAlignment == CrossAxisAlignment.stretch) {
|
795 | switch (_direction) {
|
796 | case Axis.horizontal:
|
797 | innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
|
798 | case Axis.vertical:
|
799 | innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
|
800 | }
|
801 | } else {
|
802 | switch (_direction) {
|
803 | case Axis.horizontal:
|
804 | innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
|
805 | case Axis.vertical:
|
806 | innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
|
807 | }
|
808 | }
|
809 | final Size childSize = layoutChild(child, innerConstraints);
|
810 | allocatedSize += _getMainSize(childSize);
|
811 | crossSize = math.max(crossSize, _getCrossSize(childSize));
|
812 | }
|
813 | assert(child.parentData == childParentData);
|
814 | child = childParentData.nextSibling;
|
815 | }
|
816 |
|
817 | // Distribute free space to flexible children.
|
818 | final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
|
819 | double allocatedFlexSpace = 0.0;
|
820 | if (totalFlex > 0) {
|
821 | final double spacePerFlex = canFlex ? (freeSpace / totalFlex) : double.nan;
|
822 | child = firstChild;
|
823 | while (child != null) {
|
824 | final int flex = _getFlex(child);
|
825 | if (flex > 0) {
|
826 | final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
|
827 | late final double minChildExtent;
|
828 | switch (_getFit(child)) {
|
829 | case FlexFit.tight:
|
830 | assert(maxChildExtent < double.infinity);
|
831 | minChildExtent = maxChildExtent;
|
832 | case FlexFit.loose:
|
833 | minChildExtent = 0.0;
|
834 | }
|
835 | final BoxConstraints innerConstraints;
|
836 | if (crossAxisAlignment == CrossAxisAlignment.stretch) {
|
837 | switch (_direction) {
|
838 | case Axis.horizontal:
|
839 | innerConstraints = BoxConstraints(
|
840 | minWidth: minChildExtent,
|
841 | maxWidth: maxChildExtent,
|
842 | minHeight: constraints.maxHeight,
|
843 | maxHeight: constraints.maxHeight,
|
844 | );
|
845 | case Axis.vertical:
|
846 | innerConstraints = BoxConstraints(
|
847 | minWidth: constraints.maxWidth,
|
848 | maxWidth: constraints.maxWidth,
|
849 | minHeight: minChildExtent,
|
850 | maxHeight: maxChildExtent,
|
851 | );
|
852 | }
|
853 | } else {
|
854 | switch (_direction) {
|
855 | case Axis.horizontal:
|
856 | innerConstraints = BoxConstraints(
|
857 | minWidth: minChildExtent,
|
858 | maxWidth: maxChildExtent,
|
859 | maxHeight: constraints.maxHeight,
|
860 | );
|
861 | case Axis.vertical:
|
862 | innerConstraints = BoxConstraints(
|
863 | maxWidth: constraints.maxWidth,
|
864 | minHeight: minChildExtent,
|
865 | maxHeight: maxChildExtent,
|
866 | );
|
867 | }
|
868 | }
|
869 | final Size childSize = layoutChild(child, innerConstraints);
|
870 | final double childMainSize = _getMainSize(childSize);
|
871 | assert(childMainSize <= maxChildExtent);
|
872 | allocatedSize += childMainSize;
|
873 | allocatedFlexSpace += maxChildExtent;
|
874 | crossSize = math.max(crossSize, _getCrossSize(childSize));
|
875 | }
|
876 | final FlexParentData childParentData = child.parentData! as FlexParentData;
|
877 | child = childParentData.nextSibling;
|
878 | }
|
879 | }
|
880 |
|
881 | final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
|
882 | return _LayoutSizes(
|
883 | mainSize: idealSize,
|
884 | crossSize: crossSize,
|
885 | allocatedSize: allocatedSize,
|
886 | );
|
887 | }
|
888 |
|
889 | @override
|
890 | void performLayout() {
|
891 | assert(_debugHasNecessaryDirections);
|
892 | final BoxConstraints constraints = this.constraints;
|
893 | assert(() {
|
894 | final FlutterError? constraintsError = _debugCheckConstraints(
|
895 | constraints: constraints,
|
896 | reportParentConstraints: true,
|
897 | );
|
898 | if (constraintsError != null) {
|
899 | throw constraintsError;
|
900 | }
|
901 | return true;
|
902 | }());
|
903 |
|
904 | final _LayoutSizes sizes = _computeSizes(
|
905 | layoutChild: ChildLayoutHelper.layoutChild,
|
906 | constraints: constraints,
|
907 | );
|
908 |
|
909 | final double allocatedSize = sizes.allocatedSize;
|
910 | double actualSize = sizes.mainSize;
|
911 | double crossSize = sizes.crossSize;
|
912 | double maxBaselineDistance = 0.0;
|
913 | if (crossAxisAlignment == CrossAxisAlignment.baseline) {
|
914 | RenderBox? child = firstChild;
|
915 | double maxSizeAboveBaseline = 0;
|
916 | double maxSizeBelowBaseline = 0;
|
917 | while (child != null) {
|
918 | assert(() {
|
919 | if (textBaseline == null) {
|
920 | throw FlutterError('To use CrossAxisAlignment.baseline, you must also specify which baseline to use using the "textBaseline" argument.' );
|
921 | }
|
922 | return true;
|
923 | }());
|
924 | final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
|
925 | if (distance != null) {
|
926 | maxBaselineDistance = math.max(maxBaselineDistance, distance);
|
927 | maxSizeAboveBaseline = math.max(
|
928 | distance,
|
929 | maxSizeAboveBaseline,
|
930 | );
|
931 | maxSizeBelowBaseline = math.max(
|
932 | child.size.height - distance,
|
933 | maxSizeBelowBaseline,
|
934 | );
|
935 | crossSize = math.max(maxSizeAboveBaseline + maxSizeBelowBaseline, crossSize);
|
936 | }
|
937 | final FlexParentData childParentData = child.parentData! as FlexParentData;
|
938 | child = childParentData.nextSibling;
|
939 | }
|
940 | }
|
941 |
|
942 | // Align items along the main axis.
|
943 | switch (_direction) {
|
944 | case Axis.horizontal:
|
945 | size = constraints.constrain(Size(actualSize, crossSize));
|
946 | actualSize = size.width;
|
947 | crossSize = size.height;
|
948 | case Axis.vertical:
|
949 | size = constraints.constrain(Size(crossSize, actualSize));
|
950 | actualSize = size.height;
|
951 | crossSize = size.width;
|
952 | }
|
953 | final double actualSizeDelta = actualSize - allocatedSize;
|
954 | _overflow = math.max(0.0, -actualSizeDelta);
|
955 | final double remainingSpace = math.max(0.0, actualSizeDelta);
|
956 | late final double leadingSpace;
|
957 | late final double betweenSpace;
|
958 | // flipMainAxis is used to decide whether to lay out
|
959 | // left-to-right/top-to-bottom (false), or right-to-left/bottom-to-top
|
960 | // (true). The _startIsTopLeft will return null if there's only one child
|
961 | // and the relevant direction is null, in which case we arbitrarily decide
|
962 | // to flip, but that doesn't have any detectable effect.
|
963 | final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
|
964 | switch (_mainAxisAlignment) {
|
965 | case MainAxisAlignment.start:
|
966 | leadingSpace = 0.0;
|
967 | betweenSpace = 0.0;
|
968 | case MainAxisAlignment.end:
|
969 | leadingSpace = remainingSpace;
|
970 | betweenSpace = 0.0;
|
971 | case MainAxisAlignment.center:
|
972 | leadingSpace = remainingSpace / 2.0;
|
973 | betweenSpace = 0.0;
|
974 | case MainAxisAlignment.spaceBetween:
|
975 | leadingSpace = 0.0;
|
976 | betweenSpace = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0;
|
977 | case MainAxisAlignment.spaceAround:
|
978 | betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0;
|
979 | leadingSpace = betweenSpace / 2.0;
|
980 | case MainAxisAlignment.spaceEvenly:
|
981 | betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0;
|
982 | leadingSpace = betweenSpace;
|
983 | }
|
984 |
|
985 | // Position elements
|
986 | double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
|
987 | RenderBox? child = firstChild;
|
988 | while (child != null) {
|
989 | final FlexParentData childParentData = child.parentData! as FlexParentData;
|
990 | final double childCrossPosition;
|
991 | switch (_crossAxisAlignment) {
|
992 | case CrossAxisAlignment.start:
|
993 | case CrossAxisAlignment.end:
|
994 | childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
|
995 | == (_crossAxisAlignment == CrossAxisAlignment.start)
|
996 | ? 0.0
|
997 | : crossSize - _getCrossSize(child.size);
|
998 | case CrossAxisAlignment.center:
|
999 | childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0;
|
1000 | case CrossAxisAlignment.stretch:
|
1001 | childCrossPosition = 0.0;
|
1002 | case CrossAxisAlignment.baseline:
|
1003 | if (_direction == Axis.horizontal) {
|
1004 | assert(textBaseline != null);
|
1005 | final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
|
1006 | if (distance != null) {
|
1007 | childCrossPosition = maxBaselineDistance - distance;
|
1008 | } else {
|
1009 | childCrossPosition = 0.0;
|
1010 | }
|
1011 | } else {
|
1012 | childCrossPosition = 0.0;
|
1013 | }
|
1014 | }
|
1015 | if (flipMainAxis) {
|
1016 | childMainPosition -= _getMainSize(child.size);
|
1017 | }
|
1018 | switch (_direction) {
|
1019 | case Axis.horizontal:
|
1020 | childParentData.offset = Offset(childMainPosition, childCrossPosition);
|
1021 | case Axis.vertical:
|
1022 | childParentData.offset = Offset(childCrossPosition, childMainPosition);
|
1023 | }
|
1024 | if (flipMainAxis) {
|
1025 | childMainPosition -= betweenSpace;
|
1026 | } else {
|
1027 | childMainPosition += _getMainSize(child.size) + betweenSpace;
|
1028 | }
|
1029 | child = childParentData.nextSibling;
|
1030 | }
|
1031 | }
|
1032 |
|
1033 | @override
|
1034 | bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
1035 | return defaultHitTestChildren(result, position: position);
|
1036 | }
|
1037 |
|
1038 | @override
|
1039 | void paint(PaintingContext context, Offset offset) {
|
1040 | if (!_hasOverflow) {
|
1041 | defaultPaint(context, offset);
|
1042 | return;
|
1043 | }
|
1044 |
|
1045 | // There's no point in drawing the children if we're empty.
|
1046 | if (size.isEmpty) {
|
1047 | return;
|
1048 | }
|
1049 |
|
1050 | _clipRectLayer.layer = context.pushClipRect(
|
1051 | needsCompositing,
|
1052 | offset,
|
1053 | Offset.zero & size,
|
1054 | defaultPaint,
|
1055 | clipBehavior: clipBehavior,
|
1056 | oldLayer: _clipRectLayer.layer,
|
1057 | );
|
1058 |
|
1059 | assert(() {
|
1060 | final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
|
1061 | ErrorDescription(
|
1062 | 'The overflowing $runtimeType has an orientation of $_direction.' ,
|
1063 | ),
|
1064 | ErrorDescription(
|
1065 | 'The edge of the $runtimeType that is overflowing has been marked '
|
1066 | 'in the rendering with a yellow and black striped pattern. This is '
|
1067 | 'usually caused by the contents being too big for the $runtimeType.' ,
|
1068 | ),
|
1069 | ErrorHint(
|
1070 | 'Consider applying a flex factor (e.g. using an Expanded widget) to '
|
1071 | 'force the children of the $runtimeType to fit within the available '
|
1072 | 'space instead of being sized to their natural size.' ,
|
1073 | ),
|
1074 | ErrorHint(
|
1075 | 'This is considered an error condition because it indicates that there '
|
1076 | 'is content that cannot be seen. If the content is legitimately bigger '
|
1077 | 'than the available space, consider clipping it with a ClipRect widget '
|
1078 | 'before putting it in the flex, or using a scrollable container rather '
|
1079 | 'than a Flex, like a ListView.' ,
|
1080 | ),
|
1081 | ];
|
1082 |
|
1083 | // Simulate a child rect that overflows by the right amount. This child
|
1084 | // rect is never used for drawing, just for determining the overflow
|
1085 | // location and amount.
|
1086 | final Rect overflowChildRect;
|
1087 | switch (_direction) {
|
1088 | case Axis.horizontal:
|
1089 | overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
|
1090 | case Axis.vertical:
|
1091 | overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
|
1092 | }
|
1093 | paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
|
1094 | return true;
|
1095 | }());
|
1096 | }
|
1097 |
|
1098 | final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
|
1099 |
|
1100 | @override
|
1101 | void dispose() {
|
1102 | _clipRectLayer.layer = null;
|
1103 | super.dispose();
|
1104 | }
|
1105 |
|
1106 | @override
|
1107 | Rect? describeApproximatePaintClip(RenderObject child) {
|
1108 | switch (clipBehavior) {
|
1109 | case Clip.none:
|
1110 | return null;
|
1111 | case Clip.hardEdge:
|
1112 | case Clip.antiAlias:
|
1113 | case Clip.antiAliasWithSaveLayer:
|
1114 | return _hasOverflow ? Offset.zero & size : null;
|
1115 | }
|
1116 | }
|
1117 |
|
1118 |
|
1119 | @override
|
1120 | String toStringShort() {
|
1121 | String header = super.toStringShort();
|
1122 | if (!kReleaseMode) {
|
1123 | if (_hasOverflow) {
|
1124 | header += ' OVERFLOWING' ;
|
1125 | }
|
1126 | }
|
1127 | return header;
|
1128 | }
|
1129 |
|
1130 | @override
|
1131 | void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
1132 | super.debugFillProperties(properties);
|
1133 | properties.add(EnumProperty<Axis>('direction' , direction));
|
1134 | properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment' , mainAxisAlignment));
|
1135 | properties.add(EnumProperty<MainAxisSize>('mainAxisSize' , mainAxisSize));
|
1136 | properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment' , crossAxisAlignment));
|
1137 | properties.add(EnumProperty<TextDirection>('textDirection' , textDirection, defaultValue: null));
|
1138 | properties.add(EnumProperty<VerticalDirection>('verticalDirection' , verticalDirection, defaultValue: null));
|
1139 | properties.add(EnumProperty<TextBaseline>('textBaseline' , textBaseline, defaultValue: null));
|
1140 | }
|
1141 | }
|
1142 |
|
1143 | class _LayoutSizes {
|
1144 | const _LayoutSizes({
|
1145 | required this.mainSize,
|
1146 | required this.crossSize,
|
1147 | required this.allocatedSize,
|
1148 | });
|
1149 |
|
1150 | final double mainSize;
|
1151 | final double crossSize;
|
1152 | final double allocatedSize;
|
1153 | }
|
1154 |
|