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 }) { ...@@ -50,7 +50,7 @@ void debugPrintThrottled(String message, { int wrapWidth }) {
_debugPrintTask(); _debugPrintTask();
} }
int _debugPrintedCharacters = 0; int _debugPrintedCharacters = 0;
const int _kDebugPrintCapacity = 16 * 1024; const int _kDebugPrintCapacity = 12 * 1024;
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1); const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>(); final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch(); final Stopwatch _debugPrintStopwatch = new Stopwatch();
......
...@@ -72,6 +72,11 @@ enum MainAxisSize { ...@@ -72,6 +72,11 @@ enum MainAxisSize {
/// If the incoming layout constraints have a large enough /// If the incoming layout constraints have a large enough
/// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still
/// be a non-zero amount of free space. /// 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, min,
/// Maximize the amount of free space along the main axis, subject to the /// Maximize the amount of free space along the main axis, subject to the
...@@ -80,6 +85,10 @@ enum MainAxisSize { ...@@ -80,6 +85,10 @@ enum MainAxisSize {
/// If the incoming layout constraints have a small enough /// If the incoming layout constraints have a small enough
/// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still
/// be no free space. /// 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, max,
} }
...@@ -465,11 +474,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -465,11 +474,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
final String dimension = _direction == Axis.horizontal ? 'width' : 'height'; final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
String error, message; String error, message;
String addendum = ''; 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.'; 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 ' 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 ' '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.'; 'expand to fill the remaining space in the $axis direction.';
final StringBuffer information = new StringBuffer(); final StringBuffer information = new StringBuffer();
RenderBox node = this; RenderBox node = this;
...@@ -502,6 +511,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -502,6 +511,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
'$message\n' '$message\n'
'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child ' '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' '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' 'The affected RenderFlex is:\n'
' $this\n' ' $this\n'
'The creator information is set to:\n' 'The creator information is set to:\n'
...@@ -545,21 +559,21 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -545,21 +559,21 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
_overflow = math.max(0.0, allocatedSize - (canFlex ? maxMainSize : 0.0));
// Distribute free space to flexible children, and determine baseline. // Distribute free space to flexible children, and determine baseline.
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize); final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
double maxBaselineDistance = 0.0; double maxBaselineDistance = 0.0;
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) { 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; child = firstChild;
while (child != null) { while (child != null) {
final int flex = _getFlex(child); final int flex = _getFlex(child);
if (flex > 0) { if (flex > 0) {
final double maxChildExtent = spacePerFlex * flex; final double maxChildExtent = canFlex ? spacePerFlex * flex : double.INFINITY;
double minChildExtent; double minChildExtent;
switch (_getFit(child)) { switch (_getFit(child)) {
case FlexFit.tight: case FlexFit.tight:
assert(maxChildExtent < double.INFINITY);
minChildExtent = maxChildExtent; minChildExtent = maxChildExtent;
break; break;
case FlexFit.loose: case FlexFit.loose:
...@@ -617,43 +631,43 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -617,43 +631,43 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
// Align items along the main axis. // Align items along the main axis.
double leadingSpace; double actualSizeDelta;
double betweenSpace;
double remainingSpace;
if (canFlex) { if (canFlex) {
final bool isMainAxisSizeMax = mainAxisSize == MainAxisSize.max; final bool isMainAxisSizeMax = mainAxisSize == MainAxisSize.max;
final double preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize; final double preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize;
switch (_direction) { switch (_direction) {
case Axis.horizontal: case Axis.horizontal:
size = constraints.constrain(new Size(preferredSize, crossSize)); size = constraints.constrain(new Size(preferredSize, crossSize));
remainingSpace = math.max(0.0, size.width - allocatedSize); actualSizeDelta = size.width - allocatedSize;
crossSize = size.height; crossSize = size.height;
assert(isMainAxisSizeMax ? size.width == maxMainSize : size.width >= constraints.minWidth); assert(isMainAxisSizeMax ? size.width == maxMainSize : size.width >= constraints.minWidth);
break; break;
case Axis.vertical: case Axis.vertical:
size = constraints.constrain(new Size(crossSize, preferredSize)); size = constraints.constrain(new Size(crossSize, preferredSize));
remainingSpace = math.max(0.0, size.height - allocatedSize); actualSizeDelta = size.height - allocatedSize;
crossSize = size.width; crossSize = size.width;
assert(isMainAxisSizeMax ? size.height == maxMainSize : size.height >= constraints.minHeight); assert(isMainAxisSizeMax ? size.height == maxMainSize : size.height >= constraints.minHeight);
break; break;
} }
} else { } else {
leadingSpace = 0.0;
betweenSpace = 0.0;
switch (_direction) { switch (_direction) {
case Axis.horizontal: case Axis.horizontal:
size = constraints.constrain(new Size(_overflow, crossSize)); size = constraints.constrain(new Size(allocatedSize, crossSize));
crossSize = size.height; crossSize = size.height;
remainingSpace = math.max(0.0, size.width - _overflow); actualSizeDelta = size.width - allocatedSize;
break; break;
case Axis.vertical: case Axis.vertical:
size = constraints.constrain(new Size(crossSize, _overflow)); size = constraints.constrain(new Size(crossSize, allocatedSize));
crossSize = size.width; crossSize = size.width;
remainingSpace = math.max(0.0, size.height - _overflow); actualSizeDelta = size.height - allocatedSize;
break; break;
} }
_overflow = 0.0;
} }
_overflow = math.max(0.0, -actualSizeDelta);
final double remainingSpace = math.max(0.0, actualSizeDelta);
double leadingSpace;
double betweenSpace;
switch (_mainAxisAlignment) { switch (_mainAxisAlignment) {
case MainAxisAlignment.start: case MainAxisAlignment.start:
leadingSpace = 0.0; leadingSpace = 0.0;
......
...@@ -180,4 +180,47 @@ void main() { ...@@ -180,4 +180,47 @@ void main() {
onDeleted: () {}, 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 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -75,7 +76,7 @@ void main() { ...@@ -75,7 +76,7 @@ void main() {
expect(flex.getMaxIntrinsicWidth(200.0), equals(0.0)); 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. // RenderAspectRatio being height-in, width-out.
test('Defaults', () { test('Defaults', () {
...@@ -284,4 +285,102 @@ void main() { ...@@ -284,4 +285,102 @@ void main() {
expect(box3.size.width, equals(100.0)); expect(box3.size.width, equals(100.0));
expect(flex.size.width, equals(300.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'; ...@@ -7,8 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
void main() { void main() {
testWidgets('Can hit test flex children of stacks', testWidgets('Can hit test flex children of stacks', (WidgetTester tester) async {
(WidgetTester tester) async {
bool didReceiveTap = false; bool didReceiveTap = false;
await tester.pumpWidget( await tester.pumpWidget(
new Container( 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