wrap.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. import 'dart:math' as math;
  2. import 'package:flutter/rendering.dart';
  3. class _RunMetrics {
  4. _RunMetrics(this.mainAxisExtent, this.crossAxisExtent, this.childCount);
  5. final double mainAxisExtent;
  6. final double crossAxisExtent;
  7. final int childCount;
  8. }
  9. typedef _ChildSizingFunction = double Function(RenderBox child, double extent);
  10. /// Parent data for use with [RenderWrap].
  11. class WrapWithMainAxisCountParentData extends WrapParentData {
  12. int _runIndex = 0;
  13. }
  14. class RenderWrapWithMainAxisCount extends RenderWrap {
  15. RenderWrapWithMainAxisCount({
  16. List<RenderBox>? children,
  17. Axis direction = Axis.horizontal,
  18. WrapAlignment alignment = WrapAlignment.start,
  19. double spacing = 0.0,
  20. WrapAlignment runAlignment = WrapAlignment.start,
  21. double runSpacing = 0.0,
  22. WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
  23. TextDirection? textDirection,
  24. VerticalDirection verticalDirection = VerticalDirection.down,
  25. this.minMainAxisCount,
  26. this.maxMainAxisCount,
  27. }) : assert(minMainAxisCount == null ||
  28. maxMainAxisCount == null ||
  29. maxMainAxisCount >= minMainAxisCount),
  30. // _minMainAxisCount = minMainAxisCount,
  31. // _maxMainAxisCount = maxMainAxisCount,
  32. super(
  33. children: children,
  34. direction: direction,
  35. alignment: alignment,
  36. spacing: spacing,
  37. runAlignment: runAlignment,
  38. runSpacing: runSpacing,
  39. crossAxisAlignment: crossAxisAlignment,
  40. textDirection: textDirection,
  41. verticalDirection: verticalDirection,
  42. );
  43. int? minMainAxisCount;
  44. // int get minMainAxisCount => _minMainAxisCount;
  45. // set minMainAxisCount(int value) {
  46. // _minMainAxisCount = value;
  47. // }
  48. int? maxMainAxisCount;
  49. // int get maxMainAxisCount => _maxMainAxisCount;
  50. // set maxMainAxisCount(int value) {
  51. // _maxMainAxisCount = value;
  52. // }
  53. bool get _debugHasNecessaryDirections {
  54. if (firstChild != null && lastChild != firstChild) {
  55. // i.e. there's more than one child
  56. assert(
  57. direction == Axis.vertical || textDirection != null,
  58. 'Horizontal $runtimeType with multiple children has a null '
  59. 'textDirection, so the layout order is undefined.');
  60. }
  61. if (alignment == WrapAlignment.start || alignment == WrapAlignment.end) {
  62. assert(
  63. direction == Axis.vertical || textDirection != null,
  64. 'Horizontal $runtimeType with alignment $alignment has a null '
  65. 'textDirection, so the alignment cannot be resolved.');
  66. }
  67. if (runAlignment == WrapAlignment.start ||
  68. runAlignment == WrapAlignment.end) {
  69. assert(
  70. direction == Axis.horizontal || textDirection != null,
  71. 'Horizontal $runtimeType with runAlignment $runAlignment has a null '
  72. 'verticalDirection, so the alignment cannot be resolved.');
  73. }
  74. if (crossAxisAlignment == WrapCrossAlignment.start ||
  75. crossAxisAlignment == WrapCrossAlignment.end) {
  76. assert(
  77. direction == Axis.horizontal || textDirection != null,
  78. 'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment '
  79. 'has a null textDirection, so the alignment cannot be resolved.');
  80. }
  81. return true;
  82. }
  83. @override
  84. void setupParentData(RenderBox child) {
  85. if (child.parentData is! WrapWithMainAxisCountParentData)
  86. child.parentData = WrapWithMainAxisCountParentData();
  87. }
  88. double _computeIntrinsicHeightForWidth(double width) {
  89. assert(direction == Axis.horizontal);
  90. int runCount = 0;
  91. double height = 0.0;
  92. double runWidth = 0.0;
  93. double runHeight = 0.0;
  94. int childCount = 0;
  95. RenderBox? child = firstChild;
  96. int minChildCount = minMainAxisCount ?? 1;
  97. int maxChildCount = maxMainAxisCount ?? -1;
  98. while (child != null) {
  99. final double childWidth = child.getMaxIntrinsicWidth(double.infinity);
  100. final double childHeight = child.getMaxIntrinsicHeight(childWidth);
  101. //the number of children per row/column (run) must be equal/larger than minChildCount and equal/smaller than maxChildCount.
  102. if (childCount >= minChildCount &&
  103. (runWidth + childWidth > width ||
  104. (maxChildCount >= minChildCount &&
  105. childCount >= maxChildCount))) {
  106. height += runHeight;
  107. if (runCount > 0) height += runSpacing;
  108. runCount += 1;
  109. runWidth = 0.0;
  110. runHeight = 0.0;
  111. childCount = 0;
  112. }
  113. runWidth += childWidth;
  114. runHeight = math.max(runHeight, childHeight);
  115. if (childCount > 0) runWidth += spacing;
  116. childCount += 1;
  117. child = childAfter(child);
  118. }
  119. if (childCount > 0) height += runHeight + runSpacing;
  120. return height;
  121. }
  122. double _computeIntrinsicWidthForHeight(double height) {
  123. assert(direction == Axis.vertical);
  124. int runCount = 0;
  125. double width = 0.0;
  126. double runHeight = 0.0;
  127. double runWidth = 0.0;
  128. int childCount = 0;
  129. RenderBox? child = firstChild;
  130. int minChildCount = minMainAxisCount ?? 1;
  131. int maxChildCount = maxMainAxisCount ?? -1;
  132. while (child != null) {
  133. final double childHeight = child.getMaxIntrinsicHeight(double.infinity);
  134. final double childWidth = child.getMaxIntrinsicWidth(childHeight);
  135. //the number of children per row/column (run) must be equal/larger than minChildCount and equal/smaller than maxChildCount.
  136. if (childCount >= minChildCount &&
  137. (runHeight + childHeight > height ||
  138. (maxChildCount >= minChildCount &&
  139. childCount >= maxChildCount))) {
  140. width += runWidth;
  141. if (runCount > 0) width += runSpacing;
  142. runCount += 1;
  143. runHeight = 0.0;
  144. runWidth = 0.0;
  145. childCount = 0;
  146. }
  147. runHeight += childHeight;
  148. runWidth = math.max(runWidth, childWidth);
  149. if (childCount > 0) runHeight += spacing;
  150. childCount += 1;
  151. child = childAfter(child);
  152. }
  153. if (childCount > 0) width += runWidth + runSpacing;
  154. return width;
  155. }
  156. double _getIntrinsicSize(
  157. {
  158. // Axis sizingDirection,
  159. // double extent, // the extent in the direction that isn't the sizing direction
  160. required int childCountAlongMainAxis,
  161. required _ChildSizingFunction
  162. childSize // a method to find the size in the sizing direction
  163. }) {
  164. double runMainAxisExtent = 0.0;
  165. double maxRunMainAxisExtent = 0.0;
  166. int childCount = 0;
  167. RenderBox? child = firstChild;
  168. // final List<double> runMainAxisExtents = [];
  169. while (child != null) {
  170. final double childMainAxisExtent = childSize(child, double.infinity);
  171. if (childCountAlongMainAxis > 0 &&
  172. childCount >= childCountAlongMainAxis) {
  173. maxRunMainAxisExtent =
  174. math.max(maxRunMainAxisExtent, runMainAxisExtent);
  175. runMainAxisExtent = 0.0;
  176. childCount = 0;
  177. }
  178. runMainAxisExtent += childMainAxisExtent;
  179. if (childCount > 0) runMainAxisExtent += spacing;
  180. childCount += 1;
  181. child = childAfter(child);
  182. }
  183. if (childCount > 0)
  184. maxRunMainAxisExtent = math.max(maxRunMainAxisExtent, runMainAxisExtent);
  185. return maxRunMainAxisExtent;
  186. }
  187. @override
  188. double computeMinIntrinsicWidth(double height) {
  189. switch (direction) {
  190. case Axis.horizontal:
  191. return _getIntrinsicSize(
  192. childCountAlongMainAxis: minMainAxisCount ?? 1,
  193. childSize: (RenderBox child, double extent) =>
  194. child.getMinIntrinsicWidth(extent));
  195. case Axis.vertical:
  196. return _computeIntrinsicWidthForHeight(height);
  197. }
  198. }
  199. @override
  200. double computeMaxIntrinsicWidth(double height) {
  201. switch (direction) {
  202. case Axis.horizontal:
  203. return _getIntrinsicSize(
  204. childCountAlongMainAxis: maxMainAxisCount ?? -1,
  205. childSize: (RenderBox child, double extent) =>
  206. child.getMaxIntrinsicWidth(extent));
  207. case Axis.vertical:
  208. return _computeIntrinsicWidthForHeight(height);
  209. }
  210. }
  211. @override
  212. double computeMinIntrinsicHeight(double width) {
  213. switch (direction) {
  214. case Axis.horizontal:
  215. return _computeIntrinsicHeightForWidth(width);
  216. case Axis.vertical:
  217. return _getIntrinsicSize(
  218. childCountAlongMainAxis: minMainAxisCount ?? 1,
  219. childSize: (RenderBox child, double extent) =>
  220. child.getMinIntrinsicHeight(extent));
  221. }
  222. }
  223. @override
  224. double computeMaxIntrinsicHeight(double width) {
  225. switch (direction) {
  226. case Axis.horizontal:
  227. return _computeIntrinsicHeightForWidth(width);
  228. case Axis.vertical:
  229. return _getIntrinsicSize(
  230. childCountAlongMainAxis: maxMainAxisCount ?? -1,
  231. childSize: (RenderBox child, double extent) =>
  232. child.getMaxIntrinsicHeight(extent));
  233. }
  234. }
  235. double _getMainAxisExtent(RenderBox child) {
  236. switch (direction) {
  237. case Axis.horizontal:
  238. return child.size.width;
  239. case Axis.vertical:
  240. return child.size.height;
  241. }
  242. }
  243. double _getCrossAxisExtent(RenderBox child) {
  244. switch (direction) {
  245. case Axis.horizontal:
  246. return child.size.height;
  247. case Axis.vertical:
  248. return child.size.width;
  249. }
  250. }
  251. Offset _getOffset(double mainAxisOffset, double crossAxisOffset) {
  252. switch (direction) {
  253. case Axis.horizontal:
  254. return Offset(mainAxisOffset, crossAxisOffset);
  255. case Axis.vertical:
  256. return Offset(crossAxisOffset, mainAxisOffset);
  257. }
  258. }
  259. double _getChildCrossAxisOffset(bool flipCrossAxis, double runCrossAxisExtent,
  260. double childCrossAxisExtent) {
  261. final double freeSpace = runCrossAxisExtent - childCrossAxisExtent;
  262. switch (crossAxisAlignment) {
  263. case WrapCrossAlignment.start:
  264. return flipCrossAxis ? freeSpace : 0.0;
  265. case WrapCrossAlignment.end:
  266. return flipCrossAxis ? 0.0 : freeSpace;
  267. case WrapCrossAlignment.center:
  268. return freeSpace / 2.0;
  269. }
  270. }
  271. bool _hasVisualOverflow = false;
  272. late List<int> childRunIndexes;
  273. @override
  274. void performLayout() {
  275. assert(_debugHasNecessaryDirections);
  276. _hasVisualOverflow = false;
  277. RenderBox? child = firstChild;
  278. if (child == null) {
  279. size = constraints.smallest;
  280. return;
  281. }
  282. BoxConstraints childConstraints;
  283. double mainAxisLimit = 0.0;
  284. bool flipMainAxis = false;
  285. bool flipCrossAxis = false;
  286. switch (direction) {
  287. case Axis.horizontal:
  288. childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
  289. mainAxisLimit = constraints.maxWidth;
  290. if (textDirection == TextDirection.rtl) flipMainAxis = true;
  291. if (verticalDirection == VerticalDirection.up) flipCrossAxis = true;
  292. break;
  293. case Axis.vertical:
  294. childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
  295. mainAxisLimit = constraints.maxHeight;
  296. if (verticalDirection == VerticalDirection.up) flipMainAxis = true;
  297. if (textDirection == TextDirection.rtl) flipCrossAxis = true;
  298. break;
  299. }
  300. final double spacing = this.spacing;
  301. final double runSpacing = this.runSpacing;
  302. final List<_RunMetrics> runMetrics = <_RunMetrics>[];
  303. double mainAxisExtent = 0.0;
  304. double crossAxisExtent = 0.0;
  305. double runMainAxisExtent = 0.0;
  306. double runCrossAxisExtent = 0.0;
  307. int childCount = 0;
  308. int minChildCount = minMainAxisCount ?? 1;
  309. int maxChildCount = maxMainAxisCount ?? -1;
  310. int runIndex = 0;
  311. childRunIndexes = [];
  312. while (child != null) {
  313. child.layout(childConstraints, parentUsesSize: true);
  314. final double childMainAxisExtent = _getMainAxisExtent(child);
  315. final double childCrossAxisExtent = _getCrossAxisExtent(child);
  316. //if (childCount > 0 && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
  317. if (childCount >= minChildCount &&
  318. (runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit ||
  319. (maxChildCount >= minChildCount &&
  320. childCount >= maxChildCount))) {
  321. mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
  322. crossAxisExtent += runCrossAxisExtent;
  323. if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
  324. runMetrics.add(
  325. _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
  326. runMainAxisExtent = 0.0;
  327. runCrossAxisExtent = 0.0;
  328. childCount = 0;
  329. runIndex++;
  330. }
  331. runMainAxisExtent += childMainAxisExtent;
  332. if (childCount > 0) runMainAxisExtent += spacing;
  333. runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
  334. childCount += 1;
  335. final WrapWithMainAxisCountParentData childParentData =
  336. child.parentData! as WrapWithMainAxisCountParentData;
  337. childParentData._runIndex = runMetrics.length;
  338. child = childParentData.nextSibling;
  339. childRunIndexes.add(runIndex);
  340. }
  341. if (childCount > 0) {
  342. mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
  343. crossAxisExtent += runCrossAxisExtent;
  344. if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
  345. runMetrics
  346. .add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
  347. }
  348. final int runCount = runMetrics.length;
  349. assert(runCount > 0);
  350. double containerMainAxisExtent = 0.0;
  351. double containerCrossAxisExtent = 0.0;
  352. switch (direction) {
  353. case Axis.horizontal:
  354. size = constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
  355. containerMainAxisExtent = size.width;
  356. containerCrossAxisExtent = size.height;
  357. break;
  358. case Axis.vertical:
  359. size = constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
  360. containerMainAxisExtent = size.height;
  361. containerCrossAxisExtent = size.width;
  362. break;
  363. }
  364. _hasVisualOverflow = containerMainAxisExtent < mainAxisExtent ||
  365. containerCrossAxisExtent < crossAxisExtent;
  366. final double crossAxisFreeSpace =
  367. math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
  368. double runLeadingSpace = 0.0;
  369. double runBetweenSpace = 0.0;
  370. switch (runAlignment) {
  371. case WrapAlignment.start:
  372. break;
  373. case WrapAlignment.end:
  374. runLeadingSpace = crossAxisFreeSpace;
  375. break;
  376. case WrapAlignment.center:
  377. runLeadingSpace = crossAxisFreeSpace / 2.0;
  378. break;
  379. case WrapAlignment.spaceBetween:
  380. runBetweenSpace =
  381. runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
  382. break;
  383. case WrapAlignment.spaceAround:
  384. runBetweenSpace = crossAxisFreeSpace / runCount;
  385. runLeadingSpace = runBetweenSpace / 2.0;
  386. break;
  387. case WrapAlignment.spaceEvenly:
  388. runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
  389. runLeadingSpace = runBetweenSpace;
  390. break;
  391. }
  392. runBetweenSpace += runSpacing;
  393. double crossAxisOffset = flipCrossAxis
  394. ? containerCrossAxisExtent - runLeadingSpace
  395. : runLeadingSpace;
  396. child = firstChild;
  397. for (int i = 0; i < runCount; ++i) {
  398. final _RunMetrics metrics = runMetrics[i];
  399. final double runMainAxisExtent = metrics.mainAxisExtent;
  400. final double runCrossAxisExtent = metrics.crossAxisExtent;
  401. final int childCount = metrics.childCount;
  402. final double mainAxisFreeSpace =
  403. math.max(0.0, containerMainAxisExtent - runMainAxisExtent);
  404. double childLeadingSpace = 0.0;
  405. double childBetweenSpace = 0.0;
  406. switch (alignment) {
  407. case WrapAlignment.start:
  408. break;
  409. case WrapAlignment.end:
  410. childLeadingSpace = mainAxisFreeSpace;
  411. break;
  412. case WrapAlignment.center:
  413. childLeadingSpace = mainAxisFreeSpace / 2.0;
  414. break;
  415. case WrapAlignment.spaceBetween:
  416. childBetweenSpace =
  417. childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
  418. break;
  419. case WrapAlignment.spaceAround:
  420. childBetweenSpace = mainAxisFreeSpace / childCount;
  421. childLeadingSpace = childBetweenSpace / 2.0;
  422. break;
  423. case WrapAlignment.spaceEvenly:
  424. childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
  425. childLeadingSpace = childBetweenSpace;
  426. break;
  427. }
  428. childBetweenSpace += spacing;
  429. double childMainPosition = flipMainAxis
  430. ? containerMainAxisExtent - childLeadingSpace
  431. : childLeadingSpace;
  432. if (flipCrossAxis) crossAxisOffset -= runCrossAxisExtent;
  433. while (child != null) {
  434. final WrapWithMainAxisCountParentData childParentData =
  435. child.parentData! as WrapWithMainAxisCountParentData;
  436. if (childParentData._runIndex != i) break;
  437. final double childMainAxisExtent = _getMainAxisExtent(child);
  438. final double childCrossAxisExtent = _getCrossAxisExtent(child);
  439. final double childCrossAxisOffset = _getChildCrossAxisOffset(
  440. flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
  441. if (flipMainAxis) childMainPosition -= childMainAxisExtent;
  442. childParentData.offset = _getOffset(
  443. childMainPosition, crossAxisOffset + childCrossAxisOffset);
  444. if (flipMainAxis)
  445. childMainPosition -= childBetweenSpace;
  446. else
  447. childMainPosition += childMainAxisExtent + childBetweenSpace;
  448. child = childParentData.nextSibling;
  449. }
  450. if (flipCrossAxis)
  451. crossAxisOffset -= runBetweenSpace;
  452. else
  453. crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
  454. }
  455. }
  456. @override
  457. void paint(PaintingContext context, Offset offset) {
  458. // TODO(ianh): move the debug flex overflow paint logic somewhere common so
  459. // it can be reused here
  460. if (_hasVisualOverflow)
  461. context.pushClipRect(
  462. needsCompositing, offset, Offset.zero & size, defaultPaint);
  463. else
  464. defaultPaint(context, offset);
  465. }
  466. }