From ff284c51bdd4144fd31a3d4318a9a24aa7cbd581 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:38:24 -0700 Subject: [PATCH] Implement `computeDryBaseline` for cupertino `RenderBox`es (#145951) The `_debugVerifyDryBaselines` method verifies that dry baseline implementation is consistent with the wet baseline method at the current constraints. --- .../flutter/lib/src/cupertino/nav_bar.dart | 52 ++++++++++++++----- .../lib/src/cupertino/segmented_control.dart | 12 +++++ .../cupertino/sliding_segmented_control.dart | 12 +++++ .../src/cupertino/text_selection_toolbar.dart | 37 +++++++++---- .../test/cupertino/list_tile_test.dart | 4 +- 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 1495f55d2d336..2eb9ba7addace 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -939,11 +939,45 @@ class _RenderLargeTitle extends RenderShiftedBox { double _scale = 1.0; + static double _computeTitleScale(Size childSize, BoxConstraints constraints) { + const double maxHeight = _kNavBarLargeTitleHeightExtension - _kNavBarBottomPadding; + final double scale = 1.0 + 0.03 * (constraints.maxHeight - maxHeight) / maxHeight; + final double maxScale = childSize.width != 0.0 + ? clampDouble(constraints.maxWidth / childSize.width, 1.0, 1.1) + : 1.1; + return clampDouble(scale, 1.0, maxScale); + } + @override - void performLayout() { + double? computeDistanceToActualBaseline(TextBaseline baseline) { + final double? distance = child?.getDistanceToActualBaseline(baseline); + if (distance == null) { + return null; + } + final BoxParentData childParentData = child!.parentData! as BoxParentData; + return childParentData.offset.dy + distance * _scale; + } + + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { final RenderBox? child = this.child; - Size childSize = Size.zero; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = constraints.widthConstraints().loosen(); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final double scale = _computeTitleScale(childSize, constraints); + final Size scaledChildSize = childSize * scale; + return result * scale + alignment.alongOffset(constraints.biggest - scaledChildSize as Offset).dy; + } + @override + void performLayout() { + final RenderBox? child = this.child; size = constraints.biggest; if (child == null) { @@ -952,19 +986,9 @@ class _RenderLargeTitle extends RenderShiftedBox { final BoxConstraints childConstraints = constraints.widthConstraints().loosen(); child.layout(childConstraints, parentUsesSize: true); - - final double maxScale = child.size.width != 0.0 - ? clampDouble(constraints.maxWidth / child.size.width, 1.0, 1.1) - : 1.1; - _scale = clampDouble( - 1.0 + (constraints.maxHeight - (_kNavBarLargeTitleHeightExtension - _kNavBarBottomPadding)) / (_kNavBarLargeTitleHeightExtension - _kNavBarBottomPadding) * 0.03, - 1.0, - maxScale, - ); - - childSize = child.size * _scale; + _scale = _computeTitleScale(child.size, constraints); final BoxParentData childParentData = child.parentData! as BoxParentData; - childParentData.offset = alignment.alongOffset(size - childSize as Offset); + childParentData.offset = alignment.alongOffset(size - (child.size * _scale) as Offset); } @override diff --git a/packages/flutter/lib/src/cupertino/segmented_control.dart b/packages/flutter/lib/src/cupertino/segmented_control.dart index dcb0cde6604a5..ee9f4bfb40242 100644 --- a/packages/flutter/lib/src/cupertino/segmented_control.dart +++ b/packages/flutter/lib/src/cupertino/segmented_control.dart @@ -626,6 +626,18 @@ class _RenderSegmentedControl extends RenderBox return constraints.constrain(Size(childSize.width * childCount, childSize.height)); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final Size childSize = _calculateChildSize(constraints); + final BoxConstraints childConstraints = BoxConstraints.tight(childSize); + + BaselineOffset baselineOffset = BaselineOffset.noBaseline; + for (RenderBox? child = firstChild; child != null; child = childAfter(child)) { + baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline))); + } + return baselineOffset.offset; + } + @override Size computeDryLayout(BoxConstraints constraints) { final Size childSize = _calculateChildSize(constraints); diff --git a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart index 930d44d9ef6c7..682523f318cc0 100644 --- a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart +++ b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart @@ -947,6 +947,18 @@ class _RenderSegmentedControl extends RenderBox return constraints.constrain(Size(childSize.width * childCount + totalSeparatorWidth, childSize.height)); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final Size childSize = _calculateChildSize(constraints); + final BoxConstraints childConstraints = BoxConstraints.tight(childSize); + + BaselineOffset baselineOffset = BaselineOffset.noBaseline; + for (RenderBox? child = firstChild; child != null; child = childAfter(child)) { + baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline))); + } + return baselineOffset.offset; + } + @override Size computeDryLayout(BoxConstraints constraints) { final Size childSize = _calculateChildSize(constraints); diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart index 0bd6f1db7b0eb..76cc2ee61c19b 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart @@ -276,7 +276,30 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { markNeedsPaint(); } - bool get isAbove => anchorAbove.dy >= (child?.size.height ?? 0.0) - _kToolbarArrowSize.height * 2; + bool _isAbove(double childHeight) => anchorAbove.dy >= childHeight - _kToolbarArrowSize.height * 2; + + BoxConstraints _constraintsForChild(BoxConstraints constraints) { + return BoxConstraints( + minWidth: _kToolbarArrowSize.width + _kToolbarBorderRadius.x * 2, + ).enforce(constraints.loosen()); + } + + Offset _computeChildOffset(Size childSize) { + return Offset(0.0, _isAbove(childSize.height) ? -_kToolbarArrowSize.height : 0.0); + } + + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints enforcedConstraint = _constraintsForChild(constraints); + final double? result = child.getDryBaseline(enforcedConstraint, baseline); + return result == null + ? null + : result + _computeChildOffset(child.getDryLayout(enforcedConstraint)).dy; + } @override void performLayout() { @@ -285,11 +308,7 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { return; } - final BoxConstraints enforcedConstraint = BoxConstraints( - minWidth: _kToolbarArrowSize.width + _kToolbarBorderRadius.x * 2, - ).enforce(constraints.loosen()); - child.layout(enforcedConstraint, parentUsesSize: true); - + child.layout(_constraintsForChild(constraints), parentUsesSize: true); // The buttons are padded on both top and bottom sufficiently to have // the arrow clipped out of it on either side. By // using this approach, the buttons don't need any special padding that @@ -297,10 +316,7 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { // The height of one arrow will be clipped off of the child, so adjust the // size and position to remove that piece from the layout. final BoxParentData childParentData = child.parentData! as BoxParentData; - childParentData.offset = Offset( - 0.0, - isAbove ? -_kToolbarArrowSize.height : 0.0, - ); + childParentData.offset = _computeChildOffset(child.size); size = Size( child.size.width, child.size.height - _kToolbarArrowSize.height, @@ -362,6 +378,7 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { return path..addRRect(rrect); } + final bool isAbove = _isAbove(child.size.height); final Offset localAnchor = globalToLocal(isAbove ? _anchorAbove : _anchorBelow); final double arrowTipX = clampDouble( localAnchor.dx, diff --git a/packages/flutter/test/cupertino/list_tile_test.dart b/packages/flutter/test/cupertino/list_tile_test.dart index 9ff90e82faadf..abac640546c15 100644 --- a/packages/flutter/test/cupertino/list_tile_test.dart +++ b/packages/flutter/test/cupertino/list_tile_test.dart @@ -450,7 +450,7 @@ void main() { final Offset foundTitle = tester.getTopLeft(find.text('CupertinoListTile')); final Offset foundInfo = tester.getTopRight(find.text('Not Connected')); - expect(foundTitle.dx > foundInfo.dx, isTrue); + expect(foundTitle.dx, greaterThanOrEqualTo(foundInfo.dx)); }); testWidgets('trailing is on the left of additionalInfo', (WidgetTester tester) async { @@ -476,7 +476,7 @@ void main() { final Offset foundInfo = tester.getTopLeft(find.text('Not Connected')); final Offset foundTrailing = tester.getTopRight(find.byType(CupertinoListTileChevron)); - expect(foundInfo.dx > foundTrailing.dx, isTrue); + expect(foundInfo.dx, greaterThanOrEqualTo(foundTrailing.dx)); }); });