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