From 73996ea40b6dfa399e9e84d8903ea05d40652d95 Mon Sep 17 00:00:00 2001 From: Henry Riehl <73116038+whiskeyPeak@users.noreply.github.com> Date: Fri, 7 Apr 2023 00:57:30 +0100 Subject: [PATCH] Add vertical alignment offset to the ```MenuAnchor``` widget when overflowing (#123740) Positioning of cascading menus. --- .../flutter/lib/src/material/menu_anchor.dart | 7 +- .../test/material/menu_anchor_test.dart | 99 +++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index c19f125218..793f7cad47 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -3177,7 +3177,12 @@ class _MenuLayout extends SingleChildLayoutDelegate { } else if (offBottom(y)) { final double newY = anchorRect.top - childSize.height; if (!offTop(newY)) { - y = newY; + // Only move the menu up if its parent is horizontal (MenuAchor/MenuBar). + if (parentOrientation == Axis.horizontal) { + y = newY - alignmentOffset.dy; + } else { + y = newY; + } } else { y = allowedRect.bottom - childSize.height; } diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index be05ac478e..0acd34706b 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -2481,6 +2481,105 @@ void main() { ); }); + testWidgets('vertically constrained menus are positioned above the anchor by default', (WidgetTester tester) async { + await changeSurfaceSize(tester, const Size(800, 600)); + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.bottomLeft, + child: MenuAnchor( + menuChildren: const [ + MenuItemButton(child: Text('Button1'), + ), + ], + builder: (BuildContext context, MenuController controller, Widget? child) { + return FilledButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Tap me'), + ); + }, + ), + ), + ); + }, + ), + ), + ); + + await tester.pump(); + await tester.tap(find.text('Tap me')); + await tester.pump(); + + expect(find.byType(MenuItemButton), findsNWidgets(1)); + // Test the default offset (0, 0) vertical position. + expect( + collectSubmenuRects(), + equals(const [ + Rect.fromLTRB(0.0, 488.0, 122.0, 552.0), + ]), + ); + }); + + testWidgets('vertically constrained menus are positioned above the anchor with the provided offset', (WidgetTester tester) async { + await changeSurfaceSize(tester, const Size(800, 600)); + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.bottomLeft, + child: MenuAnchor( + alignmentOffset: const Offset(0, 50), + menuChildren: const [ + MenuItemButton(child: Text('Button1'), + ), + ], + builder: (BuildContext context, MenuController controller, Widget? child) { + return FilledButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Tap me'), + ); + }, + ), + ), + ); + }, + ), + ), + ); + + await tester.pump(); + await tester.tap(find.text('Tap me')); + await tester.pump(); + + expect(find.byType(MenuItemButton), findsNWidgets(1)); + // Test the offset (0, 50) vertical position. + expect( + collectSubmenuRects(), + equals(const [ + Rect.fromLTRB(0.0, 438.0, 122.0, 502.0), + ]), + ); + }); + Future buildDensityPaddingApp(WidgetTester tester, { required TextDirection textDirection, VisualDensity visualDensity = VisualDensity.standard,