Skip to content

Commit

Permalink
Let OverflowBox be shrink-wrappable (flutter#129095)
Browse files Browse the repository at this point in the history
Close flutter#129094

I have demonstrated how this PR fixes the problem using tests in flutter#129094. I will further add tests in this PR if the PR looks roughly acceptable :)
  • Loading branch information
fzyzcjy authored Oct 25, 2023
1 parent dcbb2de commit 6df6c89
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 4 deletions.
70 changes: 66 additions & 4 deletions packages/flutter/lib/src/rendering/shifted_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
return constraints.biggest;
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;
}
}
}

Expand All @@ -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));
}
}

Expand Down
14 changes: 14 additions & 0 deletions packages/flutter/lib/src/widgets/basic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3051,6 +3051,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
this.maxWidth,
this.minHeight,
this.maxHeight,
this.fit = OverflowBoxFit.max,
super.child,
});

Expand Down Expand Up @@ -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(
Expand All @@ -3098,6 +3109,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
maxWidth: maxWidth,
minHeight: minHeight,
maxHeight: maxHeight,
fit: fit,
textDirection: Directionality.maybeOf(context),
);
}
Expand All @@ -3110,6 +3122,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
..maxWidth = maxWidth
..minHeight = minHeight
..maxHeight = maxHeight
..fit = fit
..textDirection = Directionality.maybeOf(context);
}

Expand All @@ -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));
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/flutter/test/rendering/limited_box_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
59 changes: 59 additions & 0 deletions packages/flutter/test/widgets/overflow_box_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -48,6 +106,7 @@ void main() {
'maxWidth: 2.0',
'minHeight: 3.0',
'maxHeight: 4.0',
'fit: max',
]);
});

Expand Down

0 comments on commit 6df6c89

Please sign in to comment.