Skip to content

Commit 8b88916

Browse files
committed
Make Flex shrink-wrap when unconstrained.
1 parent 88463f9 commit 8b88916

File tree

5 files changed

+193
-74
lines changed

5 files changed

+193
-74
lines changed

sky/packages/sky/lib/rendering/flex.dart

+106-69
Original file line numberDiff line numberDiff line change
@@ -289,38 +289,49 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
289289
}
290290

291291
void performLayout() {
292-
// Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexible Lengths
293-
// Steps 1-3. Determine used flex factor, size inflexible items, calculate free space
292+
// Originally based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexible Lengths
293+
294+
// Determine used flex factor, size inflexible items, calculate free space.
294295
int totalFlex = 0;
295296
int totalChildren = 0;
296297
assert(constraints != null);
297298
final double mainSize = (_direction == FlexDirection.horizontal) ? constraints.maxWidth : constraints.maxHeight;
298-
double crossSize = 0.0; // This will be determined after laying out the children
299-
double freeSpace = mainSize;
299+
final bool canFlex = mainSize < double.INFINITY;
300+
double crossSize = 0.0; // This is determined as we lay out the children
301+
double freeSpace = canFlex ? mainSize : 0.0;
300302
RenderBox child = firstChild;
301303
while (child != null) {
302304
assert(child.parentData is FlexBoxParentData);
303305
totalChildren++;
304306
int flex = _getFlex(child);
305307
if (flex > 0) {
308+
// Flexible children can only be used when the RenderFlex box's container has a finite size.
309+
// When the container is infinite, for example if you are in a scrollable viewport, then
310+
// it wouldn't make any sense to have a flexible child.
311+
assert(canFlex && 'See https://github.com/domokit/sky_engine/blob/master/sky/packages/sky/lib/widgets/flex.md' is String);
306312
totalFlex += child.parentData.flex;
307313
} else {
308314
BoxConstraints innerConstraints;
309315
if (alignItems == FlexAlignItems.stretch) {
310316
switch (_direction) {
311317
case FlexDirection.horizontal:
312-
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
313-
minHeight: constraints.minHeight,
318+
innerConstraints = new BoxConstraints(minHeight: constraints.minHeight,
314319
maxHeight: constraints.maxHeight);
315320
break;
316321
case FlexDirection.vertical:
317322
innerConstraints = new BoxConstraints(minWidth: constraints.minWidth,
318-
maxWidth: constraints.maxWidth,
319-
maxHeight: constraints.maxHeight);
323+
maxWidth: constraints.maxWidth);
320324
break;
321325
}
322326
} else {
323-
innerConstraints = constraints.loosen();
327+
switch (_direction) {
328+
case FlexDirection.horizontal:
329+
innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
330+
break;
331+
case FlexDirection.vertical:
332+
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
333+
break;
334+
}
324335
}
325336
child.layout(innerConstraints, parentUsesSize: true);
326337
freeSpace -= _getMainSize(child);
@@ -331,63 +342,98 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
331342
_overflow = math.max(0.0, -freeSpace);
332343
freeSpace = math.max(0.0, freeSpace);
333344

334-
// Steps 4-5. Distribute remaining space to flexible children.
335-
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
336-
double usedSpace = 0.0;
345+
// Distribute remaining space to flexible children, and determine baseline.
337346
double maxBaselineDistance = 0.0;
338-
child = firstChild;
339-
while (child != null) {
340-
int flex = _getFlex(child);
341-
if (flex > 0) {
342-
double spaceForChild = spacePerFlex * flex;
343-
BoxConstraints innerConstraints;
344-
if (alignItems == FlexAlignItems.stretch) {
345-
switch (_direction) {
346-
case FlexDirection.horizontal:
347-
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
348-
maxWidth: spaceForChild,
349-
minHeight: constraints.maxHeight,
350-
maxHeight: constraints.maxHeight);
351-
break;
352-
case FlexDirection.vertical:
353-
innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
354-
maxWidth: constraints.maxWidth,
355-
minHeight: spaceForChild,
356-
maxHeight: spaceForChild);
357-
break;
358-
}
359-
} else {
360-
switch (_direction) {
361-
case FlexDirection.horizontal:
362-
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
363-
maxWidth: spaceForChild,
364-
maxHeight: constraints.maxHeight);
365-
break;
366-
case FlexDirection.vertical:
367-
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
368-
minHeight: spaceForChild,
369-
maxHeight: spaceForChild);
370-
break;
347+
double usedSpace = 0.0;
348+
if (totalFlex > 0 || alignItems == FlexAlignItems.baseline) {
349+
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
350+
child = firstChild;
351+
while (child != null) {
352+
int flex = _getFlex(child);
353+
if (flex > 0) {
354+
double spaceForChild = spacePerFlex * flex;
355+
BoxConstraints innerConstraints;
356+
if (alignItems == FlexAlignItems.stretch) {
357+
switch (_direction) {
358+
case FlexDirection.horizontal:
359+
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
360+
maxWidth: spaceForChild,
361+
minHeight: constraints.maxHeight,
362+
maxHeight: constraints.maxHeight);
363+
break;
364+
case FlexDirection.vertical:
365+
innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
366+
maxWidth: constraints.maxWidth,
367+
minHeight: spaceForChild,
368+
maxHeight: spaceForChild);
369+
break;
370+
}
371+
} else {
372+
switch (_direction) {
373+
case FlexDirection.horizontal:
374+
innerConstraints = new BoxConstraints(minWidth: spaceForChild,
375+
maxWidth: spaceForChild,
376+
maxHeight: constraints.maxHeight);
377+
break;
378+
case FlexDirection.vertical:
379+
innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
380+
minHeight: spaceForChild,
381+
maxHeight: spaceForChild);
382+
break;
383+
}
371384
}
385+
child.layout(innerConstraints, parentUsesSize: true);
386+
usedSpace += _getMainSize(child);
387+
crossSize = math.max(crossSize, _getCrossSize(child));
372388
}
373-
child.layout(innerConstraints, parentUsesSize: true);
374-
usedSpace += _getMainSize(child);
375-
crossSize = math.max(crossSize, _getCrossSize(child));
376-
}
377-
if (alignItems == FlexAlignItems.baseline) {
378-
assert(textBaseline != null);
379-
double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
380-
if (distance != null)
381-
maxBaselineDistance = math.max(maxBaselineDistance, distance);
389+
if (alignItems == FlexAlignItems.baseline) {
390+
assert(textBaseline != null);
391+
double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
392+
if (distance != null)
393+
maxBaselineDistance = math.max(maxBaselineDistance, distance);
394+
}
395+
assert(child.parentData is FlexBoxParentData);
396+
child = child.parentData.nextSibling;
382397
}
383-
assert(child.parentData is FlexBoxParentData);
384-
child = child.parentData.nextSibling;
385398
}
386399

387-
// Section 8.2: Main Axis Alignment using the justify-content property
388-
double remainingSpace = math.max(0.0, freeSpace - usedSpace);
400+
// Align items along the main axis.
389401
double leadingSpace;
390402
double betweenSpace;
403+
double remainingSpace;
404+
if (canFlex) {
405+
remainingSpace = math.max(0.0, freeSpace - usedSpace);
406+
switch (_direction) {
407+
case FlexDirection.horizontal:
408+
size = constraints.constrain(new Size(mainSize, crossSize));
409+
crossSize = size.height;
410+
assert(size.width == mainSize);
411+
break;
412+
case FlexDirection.vertical:
413+
size = constraints.constrain(new Size(crossSize, mainSize));
414+
crossSize = size.width;
415+
assert(size.height == mainSize);
416+
break;
417+
}
418+
} else {
419+
leadingSpace = 0.0;
420+
betweenSpace = 0.0;
421+
switch (_direction) {
422+
case FlexDirection.horizontal:
423+
size = constraints.constrain(new Size(-_overflow, crossSize));
424+
crossSize = size.height;
425+
assert(size.width >= -_overflow);
426+
remainingSpace = size.width - -_overflow;
427+
break;
428+
case FlexDirection.vertical:
429+
size = constraints.constrain(new Size(crossSize, -_overflow));
430+
crossSize = size.width;
431+
assert(size.height >= -_overflow);
432+
remainingSpace = size.height - -_overflow;
433+
break;
434+
}
435+
_overflow = 0.0;
436+
}
391437
switch (_justifyContent) {
392438
case FlexJustifyContent.start:
393439
leadingSpace = 0.0;
@@ -411,17 +457,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
411457
break;
412458
}
413459

414-
switch (_direction) {
415-
case FlexDirection.horizontal:
416-
size = constraints.constrain(new Size(mainSize, crossSize));
417-
crossSize = size.height;
418-
break;
419-
case FlexDirection.vertical:
420-
size = constraints.constrain(new Size(crossSize, mainSize));
421-
crossSize = size.width;
422-
break;
423-
}
424-
425460
// Position elements
426461
double childMainPosition = leadingSpace;
427462
child = firstChild;
@@ -517,4 +552,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
517552
return header;
518553
}
519554

555+
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}direction: ${_direction}\n${prefix}justifyContent: ${_justifyContent}\n${prefix}alignItems: ${_alignItems}\n${prefix}textBaseline: ${_textBaseline}\n';
556+
520557
}

sky/packages/sky/lib/widgets/dialog.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ class Dialog extends Component {
7979
}
8080

8181
if (actions != null)
82-
dialogBody.add(new Flex(actions, justifyContent: FlexJustifyContent.end));
82+
dialogBody.add(new Container(
83+
child: new Flex(actions,
84+
justifyContent: FlexJustifyContent.end
85+
)
86+
));
8387

8488
return new Stack([
8589
new Listener(

sky/packages/sky/lib/widgets/flex.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
How To Use Flex In Sky
2+
======================
3+
4+
Background
5+
----------
6+
7+
In Sky, widgets are rendered by render boxes. Render boxes are given
8+
constraints by their parent, and size themselves within those
9+
constraints. Constraints consist of minimum and maximum widths and
10+
heights; sizes consist of a specific width and height.
11+
12+
Generally, there are three kinds of boxes, in terms of how they handle
13+
their constraints:
14+
15+
- Those that try to be as big as possible.
16+
For example, the boxes used by `Center` and `Block`.
17+
- Those that try to be the same size as their children.
18+
For example, the boxes used by `Transform` and `Opacity`.
19+
- Those that try to be a particular size.
20+
For example, the boxes used by `Image` and `Text`.
21+
22+
Some widgets, for example `Container`, vary from type to type based on
23+
their constructor arguments. In the case of `Container`, it defaults
24+
to trying to be as big as possible, but if you give it a `width`, for
25+
instance, it tries to honor that and be that particular size.
26+
27+
The constraints are sometimes "tight", meaning that they leave no room
28+
for the render box to decide on a size (e.g. if the minimum and
29+
maximum width are the same, it is said to have a tight width). The
30+
main example of this is the `App` widget, which is contained by the
31+
`RenderView` class: the box used by the child returned by the
32+
application's `build` function is given a constraint that forces it to
33+
exactly fill the application's content area (typically, the entire
34+
screen).
35+
36+
Unbounded constraints
37+
---------------------
38+
39+
In certain situations, the constraint that is given to a box will be
40+
_unbounded_, or infinite. This means that either the maximum width or
41+
the maximum height is set to `double.INFINITY`.
42+
43+
A box that tries to be as big as possible won't function usefully when
44+
given an unbounded constraint, and in checked mode, will assert.
45+
46+
The most common cases where a render box finds itself with unbounded
47+
constraints are within flex boxes (`Row` and `Column`), and **within
48+
scrollable regions** (mainly `Block`, `ScollableList<T>`, and
49+
`ScrollableMixedWidgetList`).
50+
51+
Flex
52+
----
53+
54+
Flex boxes themselves (`Row` and `Column`) behave differently based on
55+
whether they are in a bounded constraints or unbounded constraints in
56+
their given direction.
57+
58+
In bounded constraints, they try to be as big as possible in that
59+
direction.
60+
61+
In unbounded constraints, they try to fit their children in that
62+
direction. In this case, you cannot set `flex` on the children to
63+
anything other than 0 (the default). In the widget hierarchy, this
64+
means that you cannot use `Flexible` when the flex box is inside
65+
another flex box or inside a scrollable.
66+
67+
In the _cross_ direction, i.e. in their width for `Column` (vertical
68+
flex) and in their height for `Row` (horizontal flex), they must never
69+
be unbounded, otherwise they would not be able to reasonably align
70+
their children.

sky/packages/sky/lib/widgets/framework.dart

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:async';
66
import 'dart:collection';
77
import 'dart:sky' as sky;
88

9-
import 'package:sky/base/debug.dart';
109
import 'package:sky/base/hit_test.dart';
1110
import 'package:sky/base/scheduler.dart' as scheduler;
1211
import 'package:sky/mojo/activity.dart';

sky/packages/sky/lib/widgets/tool_bar.dart

+12-3
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ class ToolBar extends Component {
4646
}
4747

4848
List<Widget> children = new List<Widget>();
49+
50+
// left children
4951
if (left != null)
5052
children.add(left);
5153

54+
// center children (left-aligned, but takes all remaining space)
5255
children.add(
5356
new Flexible(
5457
child: new Padding(
@@ -58,15 +61,21 @@ class ToolBar extends Component {
5861
)
5962
);
6063

64+
// right children
6165
if (right != null)
6266
children.addAll(right);
6367

6468
Widget content = new Container(
6569
child: new DefaultTextStyle(
6670
style: sideStyle,
67-
child: new Flex(
68-
[new Container(child: new Flex(children), height: kToolBarHeight)],
69-
alignItems: FlexAlignItems.end
71+
child: new Flex([
72+
new Container(
73+
child: new Flex(children),
74+
height: kToolBarHeight
75+
),
76+
],
77+
direction: FlexDirection.vertical,
78+
justifyContent: FlexJustifyContent.end
7079
)
7180
),
7281
padding: new EdgeDims.symmetric(horizontal: 8.0),

0 commit comments

Comments
 (0)