Commit ce035d09 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix non-flexible Chip in a Row. (#11669)

parent 58163e0c
......@@ -50,7 +50,7 @@ void debugPrintThrottled(String message, { int wrapWidth }) {
_debugPrintTask();
}
int _debugPrintedCharacters = 0;
const int _kDebugPrintCapacity = 16 * 1024;
const int _kDebugPrintCapacity = 12 * 1024;
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch();
......
......@@ -72,6 +72,11 @@ enum MainAxisSize {
/// If the incoming layout constraints have a large enough
/// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still
/// be a non-zero amount of free space.
///
/// If the incoming layout constraints are unbounded, and any children have a
/// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by
/// [Expanded]), the [RenderFlex] will assert, because there would be infinite
/// remaining free space and boxes cannot be given infinite size.
min,
/// Maximize the amount of free space along the main axis, subject to the
......@@ -80,6 +85,10 @@ enum MainAxisSize {
/// If the incoming layout constraints have a small enough
/// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still
/// be no free space.
///
/// If the incoming layout constraints are unbounded, the [RenderFlex] will
/// assert, because there would be infinite remaining free space and boxes
/// cannot be given infinite size.
max,
}
......@@ -465,11 +474,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
String error, message;
String addendum = '';
if (maxMainSize == double.INFINITY) {
if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
error = 'RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.';
message = 'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
'axis. Setting a flex on a child (e.g. using a Flexible) indicates that the child is to '
'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
'expand to fill the remaining space in the $axis direction.';
final StringBuffer information = new StringBuffer();
RenderBox node = this;
......@@ -502,6 +511,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
'$message\n'
'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
'cannot simultaneously expand to fit its parent.\n'
'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible '
'children (using Flexible rather than Expanded). This will allow the flexible children '
'to size themselves to less than the infinite remaining space they would otherwise be '
'forced to take, and then will cause the RenderFlex to shrink-wrap the children '
'rather than expanding to fit the maximum constraints provided by the parent.\n'
'The affected RenderFlex is:\n'
' $this\n'
'The creator information is set to:\n'
......@@ -545,21 +559,21 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
_overflow = math.max(0.0, allocatedSize - (canFlex ? maxMainSize : 0.0));
// Distribute free space to flexible children, and determine baseline.
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
double maxBaselineDistance = 0.0;
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
final double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.NAN;
child = firstChild;
while (child != null) {
final int flex = _getFlex(child);
if (flex > 0) {
final double maxChildExtent = spacePerFlex * flex;
final double maxChildExtent = canFlex ? spacePerFlex * flex : double.INFINITY;
double minChildExtent;
switch (_getFit(child)) {
case FlexFit.tight:
assert(maxChildExtent < double.INFINITY);
minChildExtent = maxChildExtent;
break;
case FlexFit.loose:
......@@ -617,43 +631,43 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
// Align items along the main axis.
double leadingSpace;
double betweenSpace;
double remainingSpace;
double actualSizeDelta;
if (canFlex) {
final bool isMainAxisSizeMax = mainAxisSize == MainAxisSize.max;
final double preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize;
switch (_direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(preferredSize, crossSize));
remainingSpace = math.max(0.0, size.width - allocatedSize);
actualSizeDelta = size.width - allocatedSize;
crossSize = size.height;
assert(isMainAxisSizeMax ? size.width == maxMainSize : size.width >= constraints.minWidth);
break;
case Axis.vertical:
size = constraints.constrain(new Size(crossSize, preferredSize));
remainingSpace = math.max(0.0, size.height - allocatedSize);
actualSizeDelta = size.height - allocatedSize;
crossSize = size.width;
assert(isMainAxisSizeMax ? size.height == maxMainSize : size.height >= constraints.minHeight);
break;
}
} else {
leadingSpace = 0.0;
betweenSpace = 0.0;
switch (_direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(_overflow, crossSize));
size = constraints.constrain(new Size(allocatedSize, crossSize));
crossSize = size.height;
remainingSpace = math.max(0.0, size.width - _overflow);
actualSizeDelta = size.width - allocatedSize;
break;
case Axis.vertical:
size = constraints.constrain(new Size(crossSize, _overflow));
size = constraints.constrain(new Size(crossSize, allocatedSize));
crossSize = size.width;
remainingSpace = math.max(0.0, size.height - _overflow);
actualSizeDelta = size.height - allocatedSize;
break;
}
_overflow = 0.0;
}
_overflow = math.max(0.0, -actualSizeDelta);
final double remainingSpace = math.max(0.0, actualSizeDelta);
double leadingSpace;
double betweenSpace;
switch (_mainAxisAlignment) {
case MainAxisAlignment.start:
leadingSpace = 0.0;
......
......@@ -180,4 +180,47 @@ void main() {
onDeleted: () {},
);
});
testWidgets('Chip in row works ok', (WidgetTester tester) async {
final TextStyle style = new TextStyle(fontFamily: 'Ahem', fontSize: 10.0);
await tester.pumpWidget(
new MaterialApp(
home: new Material(
child: new Row(
children: <Widget>[
new Chip(label: new Text('Test'), labelStyle: style),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
await tester.pumpWidget(
new MaterialApp(
home: new Material(
child: new Row(
children: <Widget>[
new Flexible(child: new Chip(label: new Text('Test'), labelStyle: style)),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
await tester.pumpWidget(
new MaterialApp(
home: new Material(
child: new Row(
children: <Widget>[
new Expanded(child: new Chip(label: new Text('Test'), labelStyle: style)),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0));
});
}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -75,7 +76,7 @@ void main() {
expect(flex.getMaxIntrinsicWidth(200.0), equals(0.0));
});
// We can't right a horizontal version of the above test due to
// We can't write a horizontal version of the above test due to
// RenderAspectRatio being height-in, width-out.
test('Defaults', () {
......@@ -284,4 +285,102 @@ void main() {
expect(box3.size.width, equals(100.0));
expect(flex.size.width, equals(300.0));
});
test('MainAxisSize.min inside unconstrained', () {
FlutterError.onError = (FlutterErrorDetails details) => throw details.exception;
final BoxConstraints square = const BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.INFINITY,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(parent);
expect(flex.size, const Size(300.0, 100.0));
final FlexParentData box2ParentData = box2.parentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
flex.markNeedsLayout();
pumpFrame();
expect(flex.size, const Size(300.0, 100.0));
parent.maxWidth = 500.0; // NOW WITH CONSTRAINED BOUNDARIES
pumpFrame();
expect(flex.size, const Size(300.0, 100.0));
flex.mainAxisSize = MainAxisSize.max;
pumpFrame();
expect(flex.size, const Size(500.0, 100.0));
flex.mainAxisSize = MainAxisSize.min;
box2ParentData.fit = FlexFit.tight;
flex.markNeedsLayout();
pumpFrame();
expect(flex.size, const Size(500.0, 100.0));
parent.maxWidth = 505.0;
pumpFrame();
expect(flex.size, const Size(505.0, 100.0));
});
test('MainAxisSize.min inside unconstrained', () {
final List<dynamic> exceptions = <dynamic>[];
FlutterError.onError = (FlutterErrorDetails details) {
exceptions.add(details.exception);
};
final BoxConstraints square = const BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.INFINITY,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData;
box2ParentData.flex = 1;
expect(exceptions, isEmpty);
layout(parent);
expect(exceptions, isNotEmpty);
expect(exceptions.first, new isInstanceOf<FlutterError>());
});
test('MainAxisSize.min inside unconstrained', () {
final List<dynamic> exceptions = <dynamic>[];
FlutterError.onError = (FlutterErrorDetails details) {
exceptions.add(details.exception);
};
final BoxConstraints square = const BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
mainAxisSize: MainAxisSize.max,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.INFINITY,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
expect(exceptions, isEmpty);
layout(parent);
expect(exceptions, isNotEmpty);
expect(exceptions.first, new isInstanceOf<FlutterError>());
});
}
......@@ -7,8 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('Can hit test flex children of stacks',
(WidgetTester tester) async {
testWidgets('Can hit test flex children of stacks', (WidgetTester tester) async {
bool didReceiveTap = false;
await tester.pumpWidget(
new Container(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment