Unverified Commit 6df6c897 authored by fzyzcjy's avatar fzyzcjy Committed by GitHub

Let `OverflowBox` be shrink-wrappable (#129095)

Close https://github.com/flutter/flutter/issues/129094

I have demonstrated how this PR fixes the problem using tests in #129094. I will further add tests in this PR if the PR looks roughly acceptable :)
parent dcbb2de0
...@@ -533,6 +533,20 @@ class RenderPositionedBox extends RenderAligningShiftedBox { ...@@ -533,6 +533,20 @@ class RenderPositionedBox extends RenderAligningShiftedBox {
} }
} }
/// How much space should be occupied by the [OverflowBox] if there is no
/// overflow.
enum OverflowBoxFit {
/// The widget will size itself to be as large as the parent allows.
max,
/// The widget will follow the child's size.
///
/// More specifically, the render object will size itself to match the size of
/// its child within the constraints of its parent, or as small as the
/// parent allows if no child is set.
deferToChild,
}
/// A render object that imposes different constraints on its child than it gets /// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent. /// from its parent, possibly allowing the child to overflow the parent.
/// ///
...@@ -571,12 +585,14 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { ...@@ -571,12 +585,14 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
double? maxWidth, double? maxWidth,
double? minHeight, double? minHeight,
double? maxHeight, double? maxHeight,
OverflowBoxFit fit = OverflowBoxFit.max,
super.alignment, super.alignment,
super.textDirection, super.textDirection,
}) : _minWidth = minWidth, }) : _minWidth = minWidth,
_maxWidth = maxWidth, _maxWidth = maxWidth,
_minHeight = minHeight, _minHeight = minHeight,
_maxHeight = maxHeight; _maxHeight = maxHeight,
_fit = fit;
/// The minimum width constraint to give the child. Set this to null (the /// The minimum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead. /// default) to use the constraint from the parent instead.
...@@ -626,6 +642,24 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { ...@@ -626,6 +642,24 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
markNeedsLayout(); markNeedsLayout();
} }
/// The way to size the render object.
///
/// This only affects scenario when the child does not indeed overflow.
/// If set to [OverflowBoxFit.deferToChild], the render object will size
/// itself to match the size of its child within the constraints of its
/// parent, or as small as the parent allows if no child is set.
/// If set to [OverflowBoxFit.max] (the default), the
/// render object will size itself to be as large as the parent allows.
OverflowBoxFit get fit => _fit;
OverflowBoxFit _fit;
set fit(OverflowBoxFit value) {
if (_fit == value) {
return;
}
_fit = value;
markNeedsLayoutForSizedByParentChange();
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) { BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
return BoxConstraints( return BoxConstraints(
minWidth: _minWidth ?? constraints.minWidth, minWidth: _minWidth ?? constraints.minWidth,
...@@ -636,19 +670,46 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { ...@@ -636,19 +670,46 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
} }
@override @override
bool get sizedByParent => true; bool get sizedByParent {
switch (fit) {
case OverflowBoxFit.max:
return true;
case OverflowBoxFit.deferToChild:
// If deferToChild, the size will be as small as its child when non-overflowing,
// thus it cannot be sizedByParent.
return false;
}
}
@override @override
@protected @protected
Size computeDryLayout(covariant BoxConstraints constraints) { Size computeDryLayout(covariant BoxConstraints constraints) {
return constraints.biggest; switch (fit) {
case OverflowBoxFit.max:
return constraints.biggest;
case OverflowBoxFit.deferToChild:
return child?.getDryLayout(constraints) ?? constraints.smallest;
}
} }
@override @override
void performLayout() { void performLayout() {
if (child != null) { if (child != null) {
child?.layout(_getInnerConstraints(constraints), parentUsesSize: true); child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
switch (fit) {
case OverflowBoxFit.max:
assert(sizedByParent);
case OverflowBoxFit.deferToChild:
size = constraints.constrain(child!.size);
}
alignChild(); alignChild();
} else {
switch (fit) {
case OverflowBoxFit.max:
assert(sizedByParent);
case OverflowBoxFit.deferToChild:
size = constraints.smallest;
}
} }
} }
...@@ -659,6 +720,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { ...@@ -659,6 +720,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint')); properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint')); properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint')); properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
} }
} }
......
...@@ -3051,6 +3051,7 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -3051,6 +3051,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
this.maxWidth, this.maxWidth,
this.minHeight, this.minHeight,
this.maxHeight, this.maxHeight,
this.fit = OverflowBoxFit.max,
super.child, super.child,
}); });
...@@ -3090,6 +3091,16 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -3090,6 +3091,16 @@ class OverflowBox extends SingleChildRenderObjectWidget {
/// default) to use the constraint from the parent instead. /// default) to use the constraint from the parent instead.
final double? maxHeight; final double? maxHeight;
/// The way to size the render object.
///
/// This only affects scenario when the child does not indeed overflow.
/// If set to [OverflowBoxFit.deferToChild], the render object will size itself to
/// match the size of its child within the constraints of its parent or be
/// as small as the parent allows if no child is set. If set to
/// [OverflowBoxFit.max] (the default), the render object will size itself
/// to be as large as the parent allows.
final OverflowBoxFit fit;
@override @override
RenderConstrainedOverflowBox createRenderObject(BuildContext context) { RenderConstrainedOverflowBox createRenderObject(BuildContext context) {
return RenderConstrainedOverflowBox( return RenderConstrainedOverflowBox(
...@@ -3098,6 +3109,7 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -3098,6 +3109,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
maxWidth: maxWidth, maxWidth: maxWidth,
minHeight: minHeight, minHeight: minHeight,
maxHeight: maxHeight, maxHeight: maxHeight,
fit: fit,
textDirection: Directionality.maybeOf(context), textDirection: Directionality.maybeOf(context),
); );
} }
...@@ -3110,6 +3122,7 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -3110,6 +3122,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
..maxWidth = maxWidth ..maxWidth = maxWidth
..minHeight = minHeight ..minHeight = minHeight
..maxHeight = maxHeight ..maxHeight = maxHeight
..fit = fit
..textDirection = Directionality.maybeOf(context); ..textDirection = Directionality.maybeOf(context);
} }
...@@ -3121,6 +3134,7 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -3121,6 +3134,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: null)); properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: null));
properties.add(DoubleProperty('minHeight', minHeight, defaultValue: null)); properties.add(DoubleProperty('minHeight', minHeight, defaultValue: null));
properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: null)); properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: null));
properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
} }
} }
......
...@@ -42,6 +42,7 @@ void main() { ...@@ -42,6 +42,7 @@ void main() {
' │ maxWidth: Infinity\n' ' │ maxWidth: Infinity\n'
' │ minHeight: 0.0\n' ' │ minHeight: 0.0\n'
' │ maxHeight: Infinity\n' ' │ maxHeight: Infinity\n'
' │ fit: max\n'
' │\n' ' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE\n' ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE\n'
' │ parentData: offset=Offset(350.0, 200.0) (can use size)\n' ' │ parentData: offset=Offset(350.0, 200.0) (can use size)\n'
...@@ -128,6 +129,7 @@ void main() { ...@@ -128,6 +129,7 @@ void main() {
' │ maxWidth: 500.0\n' ' │ maxWidth: 500.0\n'
' │ minHeight: 0.0\n' ' │ minHeight: 0.0\n'
' │ maxHeight: Infinity\n' ' │ maxHeight: Infinity\n'
' │ fit: max\n'
' │\n' ' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n' ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' parentData: offset=Offset(395.0, 300.0) (can use size)\n' ' parentData: offset=Offset(395.0, 300.0) (can use size)\n'
...@@ -164,6 +166,7 @@ void main() { ...@@ -164,6 +166,7 @@ void main() {
' │ maxWidth: use parent maxWidth constraint\n' ' │ maxWidth: use parent maxWidth constraint\n'
' │ minHeight: use parent minHeight constraint\n' ' │ minHeight: use parent minHeight constraint\n'
' │ maxHeight: use parent maxHeight constraint\n' ' │ maxHeight: use parent maxHeight constraint\n'
' │ fit: max\n'
' │\n' ' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n' ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' parentData: offset=Offset(395.0, 0.0) (can use size)\n' ' parentData: offset=Offset(395.0, 0.0) (can use size)\n'
......
...@@ -31,6 +31,64 @@ void main() { ...@@ -31,6 +31,64 @@ void main() {
expect(box.size, equals(const Size(100.0, 50.0))); expect(box.size, equals(const Size(100.0, 50.0)));
}); });
// Adapted from https://github.com/flutter/flutter/issues/129094
group('when fit is OverflowBoxFit.deferToChild', () {
group('OverflowBox behavior with long and short content', () {
for (final bool contentSuperLong in <bool>[false, true]) {
testWidgetsWithLeakTracking('contentSuperLong=$contentSuperLong', (WidgetTester tester) async {
final GlobalKey<State<StatefulWidget>> key = GlobalKey();
final Column child = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 100, height: contentSuperLong ? 10000 : 100),
],
);
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Container(
key: key,
child: OverflowBox(
maxHeight: 1000000,
fit: OverflowBoxFit.deferToChild,
child: child,
),
),
],
),
));
expect(tester.getBottomLeft(find.byKey(key)).dy, contentSuperLong ? 600 : 100);
});
}
});
testWidgetsWithLeakTracking('no child', (WidgetTester tester) async {
final GlobalKey<State<StatefulWidget>> key = GlobalKey();
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Container(
key: key,
child: const OverflowBox(
maxHeight: 1000000,
fit: OverflowBoxFit.deferToChild,
// no child
),
),
],
),
));
expect(tester.getBottomLeft(find.byKey(key)).dy, 0);
});
});
testWidgetsWithLeakTracking('OverflowBox implements debugFillProperties', (WidgetTester tester) async { testWidgetsWithLeakTracking('OverflowBox implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const OverflowBox( const OverflowBox(
...@@ -48,6 +106,7 @@ void main() { ...@@ -48,6 +106,7 @@ void main() {
'maxWidth: 2.0', 'maxWidth: 2.0',
'minHeight: 3.0', 'minHeight: 3.0',
'maxHeight: 4.0', 'maxHeight: 4.0',
'fit: max',
]); ]);
}); });
......
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