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 {
}
}
/// 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
/// from its parent, possibly allowing the child to overflow the parent.
///
......@@ -571,12 +585,14 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
double? maxWidth,
double? minHeight,
double? maxHeight,
OverflowBoxFit fit = OverflowBoxFit.max,
super.alignment,
super.textDirection,
}) : _minWidth = minWidth,
_maxWidth = maxWidth,
_minHeight = minHeight,
_maxHeight = maxHeight;
_maxHeight = maxHeight,
_fit = fit;
/// The minimum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
......@@ -626,6 +642,24 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
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) {
return BoxConstraints(
minWidth: _minWidth ?? constraints.minWidth,
......@@ -636,19 +670,46 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
}
@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
@protected
Size computeDryLayout(covariant BoxConstraints constraints) {
switch (fit) {
case OverflowBoxFit.max:
return constraints.biggest;
case OverflowBoxFit.deferToChild:
return child?.getDryLayout(constraints) ?? constraints.smallest;
}
}
@override
void performLayout() {
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();
} else {
switch (fit) {
case OverflowBoxFit.max:
assert(sizedByParent);
case OverflowBoxFit.deferToChild:
size = constraints.smallest;
}
}
}
......@@ -659,6 +720,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight 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 {
this.maxWidth,
this.minHeight,
this.maxHeight,
this.fit = OverflowBoxFit.max,
super.child,
});
......@@ -3090,6 +3091,16 @@ class OverflowBox extends SingleChildRenderObjectWidget {
/// default) to use the constraint from the parent instead.
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
RenderConstrainedOverflowBox createRenderObject(BuildContext context) {
return RenderConstrainedOverflowBox(
......@@ -3098,6 +3109,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
maxWidth: maxWidth,
minHeight: minHeight,
maxHeight: maxHeight,
fit: fit,
textDirection: Directionality.maybeOf(context),
);
}
......@@ -3110,6 +3122,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
..maxWidth = maxWidth
..minHeight = minHeight
..maxHeight = maxHeight
..fit = fit
..textDirection = Directionality.maybeOf(context);
}
......@@ -3121,6 +3134,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: null));
properties.add(DoubleProperty('minHeight', minHeight, defaultValue: null));
properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: null));
properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
}
}
......
......@@ -42,6 +42,7 @@ void main() {
' │ maxWidth: Infinity\n'
' │ minHeight: 0.0\n'
' │ maxHeight: Infinity\n'
' │ fit: max\n'
' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE\n'
' │ parentData: offset=Offset(350.0, 200.0) (can use size)\n'
......@@ -128,6 +129,7 @@ void main() {
' │ maxWidth: 500.0\n'
' │ minHeight: 0.0\n'
' │ maxHeight: Infinity\n'
' │ fit: max\n'
' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' parentData: offset=Offset(395.0, 300.0) (can use size)\n'
......@@ -164,6 +166,7 @@ void main() {
' │ maxWidth: use parent maxWidth constraint\n'
' │ minHeight: use parent minHeight constraint\n'
' │ maxHeight: use parent maxHeight constraint\n'
' │ fit: max\n'
' │\n'
' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' parentData: offset=Offset(395.0, 0.0) (can use size)\n'
......
......@@ -31,6 +31,64 @@ void main() {
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 {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const OverflowBox(
......@@ -48,6 +106,7 @@ void main() {
'maxWidth: 2.0',
'minHeight: 3.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