1+ import 'dart:math' as math;
12
23import 'package:codelessly_api/codelessly_api.dart' ;
34import 'package:flutter/material.dart' ;
5+ import 'package:flutter/rendering.dart' show OverflowBoxFit;
46
57import '../../../codelessly_sdk.dart' ;
68
@@ -9,6 +11,11 @@ typedef WidgetInserter = Widget Function(Widget child);
911class PassiveRowColumnTransformer extends NodeWidgetTransformer <RowColumnNode > {
1012 PassiveRowColumnTransformer (super .getNode, super .manager);
1113
14+ /// Whether [node] is a row/column that wraps its children onto new runs
15+ /// (a [Wrap] ) instead of laying them out on one line (a [Flex] ).
16+ static bool wrapsChildren (BaseNode node) =>
17+ node is RowColumnNode && node.wrap;
18+
1219 static Widget buildRowColumnWidget (
1320 BaseNode rowColumnNode, {
1421 required List <Widget > childrenWidgets,
@@ -57,6 +64,35 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer<RowColumnNode> {
5764 ),
5865 );
5966
67+ // A wrapping row/column is a [Wrap]: the children are broken into runs
68+ // that fit its main axis. It hugs its runs on an axis it shrink-wraps and
69+ // takes the outer box's size on the others.
70+ if (wrapsChildren (rowColumnNode)) {
71+ final RowColumnNode wrapNode = rowColumnNode as RowColumnNode ;
72+ Widget wrap = Wrap (
73+ key: ValueKey (rowColumnNode.id),
74+ direction: isRow ? Axis .horizontal : Axis .vertical,
75+ alignment: wrapNode.mainAxisAlignment.flutterWrapAlignment,
76+ runAlignment: wrapNode.mainAxisAlignment.flutterWrapAlignment,
77+ crossAxisAlignment:
78+ wrapNode.crossAxisAlignment.flutterWrapCrossAlignment,
79+ spacing: math.max (0 , wrapNode.spacing),
80+ runSpacing: math.max (0 , wrapNode.runSpacing),
81+ children: childrenWidgets,
82+ );
83+ if (withScroll && wrapNode.isScrollable) {
84+ wrap = wrapWithPaddedScrollable (
85+ padding: rowColumnNode.resolvedPadding ().flutterEdgeInsets,
86+ node: wrapNode,
87+ fillViewport:
88+ rowColumnNode.mainAxisAlignment != MainAxisAlignmentC .start,
89+ flexContent: false ,
90+ child: wrap,
91+ );
92+ }
93+ return _sizeOuterBox (wrap, outerBox);
94+ }
95+
6096 final SizeFit mainFit = isRow
6197 ? rowColumnNode.horizontalFit
6298 : rowColumnNode.verticalFit;
@@ -121,6 +157,11 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer<RowColumnNode> {
121157 );
122158 }
123159
160+ return _sizeOuterBox (res, outerBox);
161+ }
162+
163+ /// Sizes a Row/Column's content [res] with its own box, [outerBox] .
164+ static Widget _sizeOuterBox (Widget res, FitBox outerBox) {
124165 if (outerBox.width == null &&
125166 outerBox.height == null &&
126167 ! outerBox.fillWidth &&
@@ -209,6 +250,31 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer<RowColumnNode> {
209250 ? node.verticalFit
210251 : node.horizontalFit);
211252
253+ // A child of a [Wrap] can't flex: there is no free space to share in a
254+ // run. An expanded child takes its own (solved) size on the main axis,
255+ // as the editor lays it out: its box gets an unbounded main axis, where
256+ // a fill falls back to its design size. The cross axis of a run is
257+ // already unbounded. A child's own alignment is not applied: an [Align]
258+ // would fill the run's main axis.
259+ if (wrapsChildren (parentRowColumn)) {
260+ if (flexibleSpaceBackground != null ) {
261+ childWidget = flexibleSpaceBackground (childWidget);
262+ }
263+ if (mainAxisFit == SizeFit .expanded) {
264+ final bool isRow = mainAxis == AxisC .horizontal;
265+ childWidget = OverflowBox (
266+ alignment: AlignmentDirectional .topStart,
267+ fit: OverflowBoxFit .deferToChild,
268+ minWidth: isRow ? 0 : null ,
269+ maxWidth: isRow ? double .infinity : null ,
270+ minHeight: isRow ? null : 0 ,
271+ maxHeight: isRow ? null : double .infinity,
272+ child: childWidget,
273+ );
274+ }
275+ return childWidget;
276+ }
277+
212278 // A parent that stretches its children across gives each one the full
213279 // cross axis, like the editor's solver, except a child that hugs on that
214280 // axis: the solver keeps its own size there and places it at the start.
@@ -395,8 +461,42 @@ class PassiveRowColumnWidget extends StatelessWidget {
395461 );
396462 }
397463
464+ /// A child of a wrapping row/column that a condition or variable hides at
465+ /// runtime is left out of the [Wrap] , so it takes no spacing either. The
466+ /// Wrap rebuilds when what decides a child's visibility changes; each
467+ /// child is keyed so the others keep their state.
468+ Widget _buildWrap (BuildContext context) {
469+ final NodeTransformerManager manager = this .manager;
470+ if (manager is ! PassiveNodeTransformerManager ) {
471+ return _buildContent (context, children);
472+ }
473+ Widget build (BuildContext context) => _buildContent (context, [
474+ for (final BaseNode child in children)
475+ if (manager.runtimeVisibility (context, child) != false ) child,
476+ ]);
477+
478+ final List <Listenable > listenables = [
479+ for (final BaseNode child in children)
480+ ...manager.listenablesFor (context, child),
481+ ];
482+ if (listenables.isEmpty) return build (context);
483+ return ManagedListenableBuilder (
484+ key: ValueKey ('${node .id }-wrap' ),
485+ listenables: listenables,
486+ builder: build,
487+ );
488+ }
489+
398490 @override
399491 Widget build (BuildContext context) {
492+ if (PassiveRowColumnTransformer .wrapsChildren (node)) {
493+ return _buildWrap (context);
494+ }
495+ return _buildContent (context, children);
496+ }
497+
498+ Widget _buildContent (BuildContext context, List <BaseNode > children) {
499+ final bool wraps = PassiveRowColumnTransformer .wrapsChildren (node);
400500 final List <Widget > widgetChildren = children.map ((child) {
401501 final Widget builtWidget = manager.buildWidgetByID (
402502 child.id,
@@ -411,6 +511,9 @@ class PassiveRowColumnWidget extends StatelessWidget {
411511 builtWidget,
412512 child.flex,
413513 );
514+ if (wraps) {
515+ return KeyedSubtree (key: ValueKey (child.id), child: sizedWidget);
516+ }
414517 return _collapseWhenHidden (context, child, sizedWidget);
415518 }).toList ();
416519
0 commit comments