diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 6117e1d938..1f9d92d4d6 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -624,8 +624,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { this.itemSizes, this.selectedItemIndex, this.textDirection, - this.topPadding, - this.bottomPadding, + this.padding, ); // Rectangle of underlying button, relative to the overlay's dimensions. @@ -642,11 +641,8 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // Whether to prefer going to the left or to the right. final TextDirection textDirection; - // Top padding of unsafe area. - final double topPadding; - - // Bottom padding of unsafe area. - final double bottomPadding; + // The padding of unsafe area. + EdgeInsets padding; // We put the child wherever position specifies, so long as it will fit within // the specified parent size padded (inset) by 8. If necessary, we adjust the @@ -657,7 +653,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // The menu can be at most the size of the overlay minus 8.0 pixels in each // direction. return BoxConstraints.loose(constraints.biggest).deflate( - const EdgeInsets.all(_kMenuScreenPadding) + EdgeInsets.only(top: topPadding, bottom: bottomPadding), + const EdgeInsets.all(_kMenuScreenPadding) + padding, ); } @@ -701,14 +697,15 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // Avoid going outside an area defined as the rectangle 8.0 pixels from the // edge of the screen in every direction. - if (x < _kMenuScreenPadding) - x = _kMenuScreenPadding; - else if (x + childSize.width > size.width - _kMenuScreenPadding) - x = size.width - childSize.width - _kMenuScreenPadding; - if (y < _kMenuScreenPadding + topPadding) - y = _kMenuScreenPadding + topPadding; - else if (y + childSize.height > size.height - _kMenuScreenPadding - bottomPadding) - y = size.height - bottomPadding - _kMenuScreenPadding - childSize.height ; + if (x < _kMenuScreenPadding + padding.left) + x = _kMenuScreenPadding + padding.left; + else if (x + childSize.width > size.width - _kMenuScreenPadding - padding.right) + x = size.width - childSize.width - _kMenuScreenPadding - padding.right ; + if (y < _kMenuScreenPadding + padding.top) + y = _kMenuScreenPadding + padding.top; + else if (y + childSize.height > size.height - _kMenuScreenPadding - padding.bottom) + y = size.height - padding.bottom - _kMenuScreenPadding - childSize.height ; + return Offset(x, y); } @@ -723,8 +720,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { || selectedItemIndex != oldDelegate.selectedItemIndex || textDirection != oldDelegate.textDirection || !listEquals(itemSizes, oldDelegate.itemSizes) - || topPadding != oldDelegate.topPadding - || bottomPadding != oldDelegate.bottomPadding; + || padding != oldDelegate.padding; } } @@ -784,22 +780,27 @@ class _PopupMenuRoute extends PopupRoute { } final Widget menu = _PopupMenu(route: this, semanticLabel: semanticLabel); - - return Builder( - builder: (BuildContext context) { - final MediaQueryData mediaQuery = MediaQuery.of(context); - return CustomSingleChildLayout( - delegate: _PopupMenuRouteLayout( - position, - itemSizes, - selectedItemIndex, - Directionality.of(context), - mediaQuery.padding.top, - mediaQuery.padding.bottom, - ), - child: capturedThemes.wrap(menu), - ); - }, + final MediaQueryData mediaQuery = MediaQuery.of(context); + return MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + removeLeft: true, + removeRight: true, + child: Builder( + builder: (BuildContext context) { + return CustomSingleChildLayout( + delegate: _PopupMenuRouteLayout( + position, + itemSizes, + selectedItemIndex, + Directionality.of(context), + mediaQuery.padding, + ), + child: capturedThemes.wrap(menu), + ); + }, + ), ); } } diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart index 04560345cf..b2fac7549c 100644 --- a/packages/flutter/test/material/popup_menu_test.dart +++ b/packages/flutter/test/material/popup_menu_test.dart @@ -2191,6 +2191,79 @@ void main() { expect(popupMenu, Offset(button.dx - 8.0, button.dy + 8.0)); }); + // Regression test for https://github.com/flutter/flutter/issues/82874 + testWidgets('PopupMenu position test when have unsafe area - left/right padding', (WidgetTester tester) async { + final GlobalKey buttonKey = GlobalKey(); + const EdgeInsets padding = EdgeInsets.only(left: 300.0, top: 32.0, right: 310.0, bottom: 64.0); + EdgeInsets? mediaQueryPadding; + + Widget buildFrame(double width, double height) { + return MaterialApp( + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: const MediaQueryData( + padding: padding, + ), + child: child!, + ); + }, + home: Scaffold( + appBar: AppBar( + title: const Text('PopupMenu Test'), + actions: [PopupMenuButton( + child: SizedBox( + key: buttonKey, + height: height, + width: width, + child: const ColoredBox( + color: Colors.pink, + ), + ), + itemBuilder: (BuildContext context) { + return >[ + PopupMenuItem( + value: 1, + child: Builder( + builder: (BuildContext context) { + mediaQueryPadding = MediaQuery.of(context).padding; + return Text('-1-' * 500); // A long long text string. + }, + ), + ), + const PopupMenuItem(value: 2, child: Text('-2-')), + ]; + }, + )], + ), + body: const SizedBox.shrink(), + ), + ); + } + + await tester.pumpWidget(buildFrame(20.0, 20.0)); + + await tester.tap(find.byKey(buttonKey)); + await tester.pumpAndSettle(); + + final Offset button = tester.getTopRight(find.byKey(buttonKey)); + expect(button, Offset(800.0 - padding.right, padding.top)); // The topPadding is 32.0. + + final Offset popupMenuTopRight = tester.getTopRight(find.byType(SingleChildScrollView)); + + // The menu should be positioned directly next to the top of the button. + // The 8.0 pixels is [_kMenuScreenPadding]. + expect(popupMenuTopRight, Offset(800.0 - padding.right - 8.0, padding.top + 8.0)); + + final Offset popupMenuTopLeft = tester.getTopLeft(find.byType(SingleChildScrollView)); + expect(popupMenuTopLeft, Offset(padding.left + 8.0, padding.top + 8.0)); + + final Offset popupMenuBottomLeft = tester.getBottomLeft(find.byType(SingleChildScrollView)); + expect(popupMenuBottomLeft, Offset(padding.left + 8.0, 600.0 - padding.bottom - 8.0)); + + // The `MediaQueryData.padding` should be removed. + expect(mediaQueryPadding, EdgeInsets.zero); + }); + group('feedback', () { late FeedbackTester feedback;