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';
6library;
7
8import 'dart:math' as math;
9
10import 'package:flutter/foundation.dart';
11
12import 'box.dart';
13import 'layer.dart';
14import 'layout_helper.dart';
15import 'object.dart';
16
17typedef _NextChild = RenderBox? Function(RenderBox child);
18typedef _PositionChild = void Function(Offset offset, RenderBox child);
19typedef _GetChildSize = Size Function(RenderBox child);
20// A 2D vector that uses a [RenderWrap]'s main axis and cross axis as its first and second coordinate axes.
21// It represents the same vector as (double mainAxisExtent, double crossAxisExtent).
22extension type const _AxisSize._(Size _size) {
23 _AxisSize({ required double mainAxisExtent, required double crossAxisExtent }) : this._(Size(mainAxisExtent, crossAxisExtent));
24 _AxisSize.fromSize({ required Size size, required Axis direction }) : this._(_convert(size, direction));
25
26 static const _AxisSize empty = _AxisSize._(Size.zero);
27
28 static Size _convert(Size size, Axis direction) {
29 return switch (direction) {
30 Axis.horizontal => size,
31 Axis.vertical => size.flipped,
32 };
33 }
34 double get mainAxisExtent => _size.width;
35 double get crossAxisExtent => _size.height;
36
37 Size toSize(Axis direction) => _convert(_size, direction);
38
39 _AxisSize applyConstraints(BoxConstraints constraints, Axis direction) {
40 final BoxConstraints effectiveConstraints = switch (direction) {
41 Axis.horizontal => constraints,
42 Axis.vertical => constraints.flipped,
43 };
44 return _AxisSize._(effectiveConstraints.constrain(_size));
45 }
46
47 _AxisSize get flipped => _AxisSize._(_size.flipped);
48 _AxisSize operator +(_AxisSize other) => _AxisSize._(Size(_size.width + other._size.width, math.max(_size.height, other._size.height)));
49 _AxisSize operator -(_AxisSize other) => _AxisSize._(Size(_size.width - other._size.width, _size.height - other._size.height));
50}
51
52/// How [Wrap] should align objects.
53///
54/// Used both to align children within a run in the main axis as well as to
55/// align the runs themselves in the cross axis.
56enum WrapAlignment {
57 /// Place the objects as close to the start of the axis as possible.
58 ///
59 /// If this value is used in a horizontal direction, a [TextDirection] must be
60 /// available to determine if the start is the left or the right.
61 ///
62 /// If this value is used in a vertical direction, a [VerticalDirection] must be
63 /// available to determine if the start is the top or the bottom.
64 start,
65
66 /// Place the objects as close to the end of the axis as possible.
67 ///
68 /// If this value is used in a horizontal direction, a [TextDirection] must be
69 /// available to determine if the end is the left or the right.
70 ///
71 /// If this value is used in a vertical direction, a [VerticalDirection] must be
72 /// available to determine if the end is the top or the bottom.
73 end,
74
75 /// Place the objects as close to the middle of the axis as possible.
76 center,
77
78 /// Place the free space evenly between the objects.
79 spaceBetween,
80
81 /// Place the free space evenly between the objects as well as half of that
82 /// space before and after the first and last objects.
83 spaceAround,
84
85 /// Place the free space evenly between the objects as well as before and
86 /// after the first and last objects.
87 spaceEvenly;
88
89 (double leadingSpace, double betweenSpace) _distributeSpace(double freeSpace, double itemSpacing, int itemCount, bool flipped) {
90 assert(itemCount > 0);
91 return switch (this) {
92 WrapAlignment.start => (flipped ? freeSpace : 0.0, itemSpacing),
93
94 WrapAlignment.end => WrapAlignment.start._distributeSpace(freeSpace, itemSpacing, itemCount, !flipped),
95 WrapAlignment.spaceBetween when itemCount < 2 => WrapAlignment.start._distributeSpace(freeSpace, itemSpacing, itemCount, flipped),
96
97 WrapAlignment.center => (freeSpace / 2.0, itemSpacing),
98 WrapAlignment.spaceBetween => (0, freeSpace / (itemCount - 1) + itemSpacing),
99 WrapAlignment.spaceAround => (freeSpace / itemCount / 2, freeSpace / itemCount + itemSpacing),
100 WrapAlignment.spaceEvenly => (freeSpace / (itemCount + 1), freeSpace / (itemCount + 1) + itemSpacing),
101 };
102 }
103}
104
105/// Who [Wrap] should align children within a run in the cross axis.
106enum WrapCrossAlignment {
107 /// Place the children as close to the start of the run in the cross axis as
108 /// possible.
109 ///
110 /// If this value is used in a horizontal direction, a [TextDirection] must be
111 /// available to determine if the start is the left or the right.
112 ///
113 /// If this value is used in a vertical direction, a [VerticalDirection] must be
114 /// available to determine if the start is the top or the bottom.
115 start,
116
117 /// Place the children as close to the end of the run in the cross axis as
118 /// possible.
119 ///
120 /// If this value is used in a horizontal direction, a [TextDirection] must be
121 /// available to determine if the end is the left or the right.
122 ///
123 /// If this value is used in a vertical direction, a [VerticalDirection] must be
124 /// available to determine if the end is the top or the bottom.
125 end,
126
127 /// Place the children as close to the middle of the run in the cross axis as
128 /// possible.
129 center;
130
131 // TODO(ianh): baseline.
132
133 WrapCrossAlignment get _flipped => switch (this) {
134 WrapCrossAlignment.start => WrapCrossAlignment.end,
135 WrapCrossAlignment.end => WrapCrossAlignment.start,
136 WrapCrossAlignment.center => WrapCrossAlignment.center,
137 };
138
139 double get _alignment => switch (this) {
140 WrapCrossAlignment.start => 0,
141 WrapCrossAlignment.end => 1,
142 WrapCrossAlignment.center => 0.5,
143 };
144}
145
146class _RunMetrics {
147 _RunMetrics(this.leadingChild, this.axisSize);
148
149 _AxisSize axisSize;
150 int childCount = 1;
151 RenderBox leadingChild;
152
153 // Look ahead, creates a new run if incorporating the child would exceed the allowed line width.
154 _RunMetrics? tryAddingNewChild(RenderBox child, _AxisSize childSize, bool flipMainAxis, double spacing, double maxMainExtent) {
155 final bool needsNewRun = axisSize.mainAxisExtent + childSize.mainAxisExtent + spacing - maxMainExtent > precisionErrorTolerance;
156 if (needsNewRun) {
157 return _RunMetrics(child, childSize);
158 } else {
159 axisSize += childSize + _AxisSize(mainAxisExtent: spacing, crossAxisExtent: 0.0);
160 childCount += 1;
161 if (flipMainAxis) {
162 leadingChild = child;
163 }
164 return null;
165 }
166 }
167}
168
169/// Parent data for use with [RenderWrap].
170class WrapParentData extends ContainerBoxParentData<RenderBox> { }
171
172/// Displays its children in multiple horizontal or vertical runs.
173///
174/// A [RenderWrap] lays out each child and attempts to place the child adjacent
175/// to the previous child in the main axis, given by [direction], leaving
176/// [spacing] space in between. If there is not enough space to fit the child,
177/// [RenderWrap] creates a new _run_ adjacent to the existing children in the
178/// cross axis.
179///
180/// After all the children have been allocated to runs, the children within the
181/// runs are positioned according to the [alignment] in the main axis and
182/// according to the [crossAxisAlignment] in the cross axis.
183///
184/// The runs themselves are then positioned in the cross axis according to the
185/// [runSpacing] and [runAlignment].
186class RenderWrap extends RenderBox
187 with ContainerRenderObjectMixin<RenderBox, WrapParentData>,
188 RenderBoxContainerDefaultsMixin<RenderBox, WrapParentData> {
189 /// Creates a wrap render object.
190 ///
191 /// By default, the wrap layout is horizontal and both the children and the
192 /// runs are aligned to the start.
193 RenderWrap({
194 List<RenderBox>? children,
195 Axis direction = Axis.horizontal,
196 WrapAlignment alignment = WrapAlignment.start,
197 double spacing = 0.0,
198 WrapAlignment runAlignment = WrapAlignment.start,
199 double runSpacing = 0.0,
200 WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
201 TextDirection? textDirection,
202 VerticalDirection verticalDirection = VerticalDirection.down,
203 Clip clipBehavior = Clip.none,
204 }) : _direction = direction,
205 _alignment = alignment,
206 _spacing = spacing,
207 _runAlignment = runAlignment,
208 _runSpacing = runSpacing,
209 _crossAxisAlignment = crossAxisAlignment,
210 _textDirection = textDirection,
211 _verticalDirection = verticalDirection,
212 _clipBehavior = clipBehavior {
213 addAll(children);
214 }
215
216 /// The direction to use as the main axis.
217 ///
218 /// For example, if [direction] is [Axis.horizontal], the default, the
219 /// children are placed adjacent to one another in a horizontal run until the
220 /// available horizontal space is consumed, at which point a subsequent
221 /// children are placed in a new run vertically adjacent to the previous run.
222 Axis get direction => _direction;
223 Axis _direction;
224 set direction (Axis value) {
225 if (_direction == value) {
226 return;
227 }
228 _direction = value;
229 markNeedsLayout();
230 }
231
232 /// How the children within a run should be placed in the main axis.
233 ///
234 /// For example, if [alignment] is [WrapAlignment.center], the children in
235 /// each run are grouped together in the center of their run in the main axis.
236 ///
237 /// Defaults to [WrapAlignment.start].
238 ///
239 /// See also:
240 ///
241 /// * [runAlignment], which controls how the runs are placed relative to each
242 /// other in the cross axis.
243 /// * [crossAxisAlignment], which controls how the children within each run
244 /// are placed relative to each other in the cross axis.
245 WrapAlignment get alignment => _alignment;
246 WrapAlignment _alignment;
247 set alignment (WrapAlignment value) {
248 if (_alignment == value) {
249 return;
250 }
251 _alignment = value;
252 markNeedsLayout();
253 }
254
255 /// How much space to place between children in a run in the main axis.
256 ///
257 /// For example, if [spacing] is 10.0, the children will be spaced at least
258 /// 10.0 logical pixels apart in the main axis.
259 ///
260 /// If there is additional free space in a run (e.g., because the wrap has a
261 /// minimum size that is not filled or because some runs are longer than
262 /// others), the additional free space will be allocated according to the
263 /// [alignment].
264 ///
265 /// Defaults to 0.0.
266 double get spacing => _spacing;
267 double _spacing;
268 set spacing (double value) {
269 if (_spacing == value) {
270 return;
271 }
272 _spacing = value;
273 markNeedsLayout();
274 }
275
276 /// How the runs themselves should be placed in the cross axis.
277 ///
278 /// For example, if [runAlignment] is [WrapAlignment.center], the runs are
279 /// grouped together in the center of the overall [RenderWrap] in the cross
280 /// axis.
281 ///
282 /// Defaults to [WrapAlignment.start].
283 ///
284 /// See also:
285 ///
286 /// * [alignment], which controls how the children within each run are placed
287 /// relative to each other in the main axis.
288 /// * [crossAxisAlignment], which controls how the children within each run
289 /// are placed relative to each other in the cross axis.
290 WrapAlignment get runAlignment => _runAlignment;
291 WrapAlignment _runAlignment;
292 set runAlignment (WrapAlignment value) {
293 if (_runAlignment == value) {
294 return;
295 }
296 _runAlignment = value;
297 markNeedsLayout();
298 }
299
300 /// How much space to place between the runs themselves in the cross axis.
301 ///
302 /// For example, if [runSpacing] is 10.0, the runs will be spaced at least
303 /// 10.0 logical pixels apart in the cross axis.
304 ///
305 /// If there is additional free space in the overall [RenderWrap] (e.g.,
306 /// because the wrap has a minimum size that is not filled), the additional
307 /// free space will be allocated according to the [runAlignment].
308 ///
309 /// Defaults to 0.0.
310 double get runSpacing => _runSpacing;
311 double _runSpacing;
312 set runSpacing (double value) {
313 if (_runSpacing == value) {
314 return;
315 }
316 _runSpacing = value;
317 markNeedsLayout();
318 }
319
320 /// How the children within a run should be aligned relative to each other in
321 /// the cross axis.
322 ///
323 /// For example, if this is set to [WrapCrossAlignment.end], and the
324 /// [direction] is [Axis.horizontal], then the children within each
325 /// run will have their bottom edges aligned to the bottom edge of the run.
326 ///
327 /// Defaults to [WrapCrossAlignment.start].
328 ///
329 /// See also:
330 ///
331 /// * [alignment], which controls how the children within each run are placed
332 /// relative to each other in the main axis.
333 /// * [runAlignment], which controls how the runs are placed relative to each
334 /// other in the cross axis.
335 WrapCrossAlignment get crossAxisAlignment => _crossAxisAlignment;
336 WrapCrossAlignment _crossAxisAlignment;
337 set crossAxisAlignment (WrapCrossAlignment value) {
338 if (_crossAxisAlignment == value) {
339 return;
340 }
341 _crossAxisAlignment = value;
342 markNeedsLayout();
343 }
344
345 /// Determines the order to lay children out horizontally and how to interpret
346 /// `start` and `end` in the horizontal direction.
347 ///
348 /// If the [direction] is [Axis.horizontal], this controls the order in which
349 /// children are positioned (left-to-right or right-to-left), and the meaning
350 /// of the [alignment] property's [WrapAlignment.start] and
351 /// [WrapAlignment.end] values.
352 ///
353 /// If the [direction] is [Axis.horizontal], and either the
354 /// [alignment] is either [WrapAlignment.start] or [WrapAlignment.end], or
355 /// there's more than one child, then the [textDirection] must not be null.
356 ///
357 /// If the [direction] is [Axis.vertical], this controls the order in
358 /// which runs are positioned, the meaning of the [runAlignment] property's
359 /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
360 /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
361 /// [WrapCrossAlignment.end] values.
362 ///
363 /// If the [direction] is [Axis.vertical], and either the
364 /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
365 /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
366 /// [WrapCrossAlignment.end], or there's more than one child, then the
367 /// [textDirection] must not be null.
368 TextDirection? get textDirection => _textDirection;
369 TextDirection? _textDirection;
370 set textDirection(TextDirection? value) {
371 if (_textDirection != value) {
372 _textDirection = value;
373 markNeedsLayout();
374 }
375 }
376
377 /// Determines the order to lay children out vertically and how to interpret
378 /// `start` and `end` in the vertical direction.
379 ///
380 /// If the [direction] is [Axis.vertical], this controls which order children
381 /// are painted in (down or up), the meaning of the [alignment] property's
382 /// [WrapAlignment.start] and [WrapAlignment.end] values.
383 ///
384 /// If the [direction] is [Axis.vertical], and either the [alignment]
385 /// is either [WrapAlignment.start] or [WrapAlignment.end], or there's
386 /// more than one child, then the [verticalDirection] must not be null.
387 ///
388 /// If the [direction] is [Axis.horizontal], this controls the order in which
389 /// runs are positioned, the meaning of the [runAlignment] property's
390 /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
391 /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
392 /// [WrapCrossAlignment.end] values.
393 ///
394 /// If the [direction] is [Axis.horizontal], and either the
395 /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
396 /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
397 /// [WrapCrossAlignment.end], or there's more than one child, then the
398 /// [verticalDirection] must not be null.
399 VerticalDirection get verticalDirection => _verticalDirection;
400 VerticalDirection _verticalDirection;
401 set verticalDirection(VerticalDirection value) {
402 if (_verticalDirection != value) {
403 _verticalDirection = value;
404 markNeedsLayout();
405 }
406 }
407
408 /// {@macro flutter.material.Material.clipBehavior}
409 ///
410 /// Defaults to [Clip.none].
411 Clip get clipBehavior => _clipBehavior;
412 Clip _clipBehavior = Clip.none;
413 set clipBehavior(Clip value) {
414 if (value != _clipBehavior) {
415 _clipBehavior = value;
416 markNeedsPaint();
417 markNeedsSemanticsUpdate();
418 }
419 }
420
421 bool get _debugHasNecessaryDirections {
422 if (firstChild != null && lastChild != firstChild) {
423 // i.e. there's more than one child
424 switch (direction) {
425 case Axis.horizontal:
426 assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
427 case Axis.vertical:
428 break;
429 }
430 }
431 if (alignment == WrapAlignment.start || alignment == WrapAlignment.end) {
432 switch (direction) {
433 case Axis.horizontal:
434 assert(textDirection != null, 'Horizontal $runtimeType with alignment $alignment has a null textDirection, so the alignment cannot be resolved.');
435 case Axis.vertical:
436 break;
437 }
438 }
439 if (runAlignment == WrapAlignment.start || runAlignment == WrapAlignment.end) {
440 switch (direction) {
441 case Axis.horizontal:
442 break;
443 case Axis.vertical:
444 assert(textDirection != null, 'Vertical $runtimeType with runAlignment $runAlignment has a null textDirection, so the alignment cannot be resolved.');
445 }
446 }
447 if (crossAxisAlignment == WrapCrossAlignment.start || crossAxisAlignment == WrapCrossAlignment.end) {
448 switch (direction) {
449 case Axis.horizontal:
450 break;
451 case Axis.vertical:
452 assert(textDirection != null, 'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
453 }
454 }
455 return true;
456 }
457
458 @override
459 void setupParentData(RenderBox child) {
460 if (child.parentData is! WrapParentData) {
461 child.parentData = WrapParentData();
462 }
463 }
464
465 @override
466 double computeMinIntrinsicWidth(double height) {
467 switch (direction) {
468 case Axis.horizontal:
469 double width = 0.0;
470 RenderBox? child = firstChild;
471 while (child != null) {
472 width = math.max(width, child.getMinIntrinsicWidth(double.infinity));
473 child = childAfter(child);
474 }
475 return width;
476 case Axis.vertical:
477 return getDryLayout(BoxConstraints(maxHeight: height)).width;
478 }
479 }
480
481 @override
482 double computeMaxIntrinsicWidth(double height) {
483 switch (direction) {
484 case Axis.horizontal:
485 double width = 0.0;
486 RenderBox? child = firstChild;
487 while (child != null) {
488 width += child.getMaxIntrinsicWidth(double.infinity);
489 child = childAfter(child);
490 }
491 return width;
492 case Axis.vertical:
493 return getDryLayout(BoxConstraints(maxHeight: height)).width;
494 }
495 }
496
497 @override
498 double computeMinIntrinsicHeight(double width) {
499 switch (direction) {
500 case Axis.horizontal:
501 return getDryLayout(BoxConstraints(maxWidth: width)).height;
502 case Axis.vertical:
503 double height = 0.0;
504 RenderBox? child = firstChild;
505 while (child != null) {
506 height = math.max(height, child.getMinIntrinsicHeight(double.infinity));
507 child = childAfter(child);
508 }
509 return height;
510 }
511 }
512
513 @override
514 double computeMaxIntrinsicHeight(double width) {
515 switch (direction) {
516 case Axis.horizontal:
517 return getDryLayout(BoxConstraints(maxWidth: width)).height;
518 case Axis.vertical:
519 double height = 0.0;
520 RenderBox? child = firstChild;
521 while (child != null) {
522 height += child.getMaxIntrinsicHeight(double.infinity);
523 child = childAfter(child);
524 }
525 return height;
526 }
527 }
528
529 @override
530 double? computeDistanceToActualBaseline(TextBaseline baseline) {
531 return defaultComputeDistanceToHighestActualBaseline(baseline);
532 }
533
534 double _getMainAxisExtent(Size childSize) {
535 return switch (direction) {
536 Axis.horizontal => childSize.width,
537 Axis.vertical => childSize.height,
538 };
539 }
540
541 double _getCrossAxisExtent(Size childSize) {
542 return switch (direction) {
543 Axis.horizontal => childSize.height,
544 Axis.vertical => childSize.width,
545 };
546 }
547
548 Offset _getOffset(double mainAxisOffset, double crossAxisOffset) {
549 return switch (direction) {
550 Axis.horizontal => Offset(mainAxisOffset, crossAxisOffset),
551 Axis.vertical => Offset(crossAxisOffset, mainAxisOffset),
552 };
553 }
554
555 (bool flipHorizontal, bool flipVertical) get _areAxesFlipped {
556 final bool flipHorizontal = switch (textDirection ?? TextDirection.ltr) {
557 TextDirection.ltr => false,
558 TextDirection.rtl => true,
559 };
560 final bool flipVertical = switch (verticalDirection) {
561 VerticalDirection.down => false,
562 VerticalDirection.up => true,
563 };
564 return switch (direction) {
565 Axis.horizontal => (flipHorizontal, flipVertical),
566 Axis.vertical => (flipVertical, flipHorizontal),
567 };
568 }
569
570 @override
571 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
572 if (firstChild == null) {
573 return null;
574 }
575 final BoxConstraints childConstraints = switch (direction) {
576 Axis.horizontal => BoxConstraints(maxWidth: constraints.maxWidth),
577 Axis.vertical => BoxConstraints(maxHeight: constraints.maxHeight),
578 };
579
580 final (_AxisSize childrenAxisSize, List<_RunMetrics> runMetrics) = _computeRuns(constraints, ChildLayoutHelper.dryLayoutChild);
581 final _AxisSize containerAxisSize = childrenAxisSize.applyConstraints(constraints, direction);
582
583 BaselineOffset baselineOffset = BaselineOffset.noBaseline;
584 void findHighestBaseline(Offset offset, RenderBox child) {
585 baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline)) + offset.dy);
586 }
587 Size getChildSize(RenderBox child) => child.getDryLayout(childConstraints);
588 _positionChildren(runMetrics, childrenAxisSize, containerAxisSize, findHighestBaseline, getChildSize);
589 return baselineOffset.offset;
590 }
591
592 @override
593 @protected
594 Size computeDryLayout(covariant BoxConstraints constraints) {
595 return _computeDryLayout(constraints);
596 }
597
598 Size _computeDryLayout(BoxConstraints constraints, [ChildLayouter layoutChild = ChildLayoutHelper.dryLayoutChild]) {
599 final (BoxConstraints childConstraints, double mainAxisLimit) = switch (direction) {
600 Axis.horizontal => (BoxConstraints(maxWidth: constraints.maxWidth), constraints.maxWidth),
601 Axis.vertical => (BoxConstraints(maxHeight: constraints.maxHeight), constraints.maxHeight),
602 };
603
604 double mainAxisExtent = 0.0;
605 double crossAxisExtent = 0.0;
606 double runMainAxisExtent = 0.0;
607 double runCrossAxisExtent = 0.0;
608 int childCount = 0;
609 RenderBox? child = firstChild;
610 while (child != null) {
611 final Size childSize = layoutChild(child, childConstraints);
612 final double childMainAxisExtent = _getMainAxisExtent(childSize);
613 final double childCrossAxisExtent = _getCrossAxisExtent(childSize);
614 // There must be at least one child before we move on to the next run.
615 if (childCount > 0 && runMainAxisExtent + childMainAxisExtent + spacing > mainAxisLimit) {
616 mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
617 crossAxisExtent += runCrossAxisExtent + runSpacing;
618 runMainAxisExtent = 0.0;
619 runCrossAxisExtent = 0.0;
620 childCount = 0;
621 }
622 runMainAxisExtent += childMainAxisExtent;
623 runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
624 if (childCount > 0) {
625 runMainAxisExtent += spacing;
626 }
627 childCount += 1;
628 child = childAfter(child);
629 }
630 crossAxisExtent += runCrossAxisExtent;
631 mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
632
633 return constraints.constrain(switch (direction) {
634 Axis.horizontal => Size(mainAxisExtent, crossAxisExtent),
635 Axis.vertical => Size(crossAxisExtent, mainAxisExtent),
636 });
637 }
638
639 static Size _getChildSize(RenderBox child) => child.size;
640 static void _setChildPosition(Offset offset, RenderBox child) {
641 (child.parentData! as WrapParentData).offset = offset;
642 }
643
644 bool _hasVisualOverflow = false;
645
646 @override
647 void performLayout() {
648 final BoxConstraints constraints = this.constraints;
649 assert(_debugHasNecessaryDirections);
650 if (firstChild == null) {
651 size = constraints.smallest;
652 _hasVisualOverflow = false;
653 return;
654 }
655
656 final (_AxisSize childrenAxisSize, List<_RunMetrics> runMetrics) = _computeRuns(constraints, ChildLayoutHelper.layoutChild);
657 final _AxisSize containerAxisSize = childrenAxisSize.applyConstraints(constraints, direction);
658 size = containerAxisSize.toSize(direction);
659 final _AxisSize freeAxisSize = containerAxisSize - childrenAxisSize;
660 _hasVisualOverflow = freeAxisSize.mainAxisExtent < 0.0 || freeAxisSize.crossAxisExtent < 0.0;
661 _positionChildren(runMetrics, freeAxisSize, containerAxisSize, _setChildPosition, _getChildSize);
662 }
663
664 (_AxisSize childrenSize, List<_RunMetrics> runMetrics) _computeRuns(BoxConstraints constraints, ChildLayouter layoutChild) {
665 assert(firstChild != null);
666 final (BoxConstraints childConstraints, double mainAxisLimit) = switch (direction) {
667 Axis.horizontal => (BoxConstraints(maxWidth: constraints.maxWidth), constraints.maxWidth),
668 Axis.vertical => (BoxConstraints(maxHeight: constraints.maxHeight), constraints.maxHeight),
669 };
670
671 final (bool flipMainAxis, _) = _areAxesFlipped;
672 final double spacing = this.spacing;
673 final List<_RunMetrics> runMetrics = <_RunMetrics>[];
674
675 _RunMetrics? currentRun;
676 _AxisSize childrenAxisSize = _AxisSize.empty;
677 for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
678 final _AxisSize childSize = _AxisSize.fromSize(size: layoutChild(child, childConstraints), direction: direction);
679 final _RunMetrics? newRun = currentRun == null
680 ? _RunMetrics(child, childSize)
681 : currentRun.tryAddingNewChild(child, childSize, flipMainAxis, spacing, mainAxisLimit);
682 if (newRun != null) {
683 runMetrics.add(newRun);
684 childrenAxisSize += currentRun?.axisSize.flipped ?? _AxisSize.empty;
685 currentRun = newRun;
686 }
687 }
688 assert(runMetrics.isNotEmpty);
689 final double totalRunSpacing = runSpacing * (runMetrics.length - 1);
690 childrenAxisSize += _AxisSize(mainAxisExtent: totalRunSpacing, crossAxisExtent: 0.0) + currentRun!.axisSize.flipped;
691 return (childrenAxisSize.flipped, runMetrics);
692 }
693
694 void _positionChildren(List<_RunMetrics> runMetrics, _AxisSize freeAxisSize, _AxisSize containerAxisSize, _PositionChild positionChild, _GetChildSize getChildSize) {
695 assert(runMetrics.isNotEmpty);
696
697 final double spacing = this.spacing;
698
699 final double crossAxisFreeSpace = math.max(0.0, freeAxisSize.crossAxisExtent);
700
701 final (bool flipMainAxis, bool flipCrossAxis) = _areAxesFlipped;
702 final WrapCrossAlignment effectiveCrossAlignment = flipCrossAxis ? crossAxisAlignment._flipped : crossAxisAlignment;
703 final (double runLeadingSpace, double runBetweenSpace) = runAlignment._distributeSpace(
704 crossAxisFreeSpace,
705 runSpacing,
706 runMetrics.length,
707 flipCrossAxis,
708 );
709 final _NextChild nextChild = flipMainAxis ? childBefore : childAfter;
710
711 double runCrossAxisOffset = runLeadingSpace;
712 final Iterable<_RunMetrics> runs = flipCrossAxis ? runMetrics.reversed : runMetrics;
713 for (final _RunMetrics run in runs) {
714 final double runCrossAxisExtent = run.axisSize.crossAxisExtent;
715 final int childCount = run.childCount;
716
717 final double mainAxisFreeSpace = math.max(0.0, containerAxisSize.mainAxisExtent - run.axisSize.mainAxisExtent);
718 final (double childLeadingSpace, double childBetweenSpace) = alignment._distributeSpace(mainAxisFreeSpace, spacing, childCount, flipMainAxis);
719
720 double childMainAxisOffset = childLeadingSpace;
721
722 int remainingChildCount = run.childCount;
723 for (RenderBox? child = run.leadingChild; child != null && remainingChildCount > 0; child = nextChild(child), remainingChildCount -= 1) {
724 final _AxisSize(mainAxisExtent: double childMainAxisExtent, crossAxisExtent: double childCrossAxisExtent) = _AxisSize.fromSize(size: getChildSize(child), direction: direction);
725 final double childCrossAxisOffset = effectiveCrossAlignment._alignment * (runCrossAxisExtent - childCrossAxisExtent);
726 positionChild(_getOffset(childMainAxisOffset, runCrossAxisOffset + childCrossAxisOffset), child);
727 childMainAxisOffset += childMainAxisExtent + childBetweenSpace;
728 }
729 runCrossAxisOffset += runCrossAxisExtent + runBetweenSpace;
730 }
731 }
732
733 @override
734 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
735 return defaultHitTestChildren(result, position: position);
736 }
737
738 @override
739 void paint(PaintingContext context, Offset offset) {
740 // TODO(ianh): move the debug flex overflow paint logic somewhere common so
741 // it can be reused here
742 if (_hasVisualOverflow && clipBehavior != Clip.none) {
743 _clipRectLayer.layer = context.pushClipRect(
744 needsCompositing,
745 offset,
746 Offset.zero & size,
747 defaultPaint,
748 clipBehavior: clipBehavior,
749 oldLayer: _clipRectLayer.layer,
750 );
751 } else {
752 _clipRectLayer.layer = null;
753 defaultPaint(context, offset);
754 }
755 }
756
757 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
758
759 @override
760 void dispose() {
761 _clipRectLayer.layer = null;
762 super.dispose();
763 }
764
765 @override
766 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
767 super.debugFillProperties(properties);
768 properties.add(EnumProperty<Axis>('direction', direction));
769 properties.add(EnumProperty<WrapAlignment>('alignment', alignment));
770 properties.add(DoubleProperty('spacing', spacing));
771 properties.add(EnumProperty<WrapAlignment>('runAlignment', runAlignment));
772 properties.add(DoubleProperty('runSpacing', runSpacing));
773 properties.add(DoubleProperty('crossAxisAlignment', runSpacing));
774 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
775 properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
776 }
777}
778