diff --git a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart index 6c08ac58efc..cb5c29d1fdd 100644 --- a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart +++ b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart @@ -262,6 +262,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { buttons.add(TextSelectionToolbarTextButton( padding: TextSelectionToolbarTextButton.getPadding(i, buttonItems.length), onPressed: buttonItem.onPressed, + alignment: AlignmentDirectional.centerStart, child: Text(getButtonLabel(context, buttonItem)), )); } diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart index a38ea8af27a..bb210060e6c 100644 --- a/packages/flutter/lib/src/material/text_selection.dart +++ b/packages/flutter/lib/src/material/text_selection.dart @@ -267,6 +267,7 @@ class _TextSelectionControlsToolbarState extends State<_TextSelectionControlsToo children: itemDatas.asMap().entries.map((MapEntry entry) { return TextSelectionToolbarTextButton( padding: TextSelectionToolbarTextButton.getPadding(entry.key, itemDatas.length), + alignment: AlignmentDirectional.centerStart, onPressed: entry.value.onPressed, child: Text(entry.value.label), ); diff --git a/packages/flutter/lib/src/material/text_selection_toolbar.dart b/packages/flutter/lib/src/material/text_selection_toolbar.dart index 61b047a3cc9..96f00227cf8 100644 --- a/packages/flutter/lib/src/material/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/material/text_selection_toolbar.dart @@ -569,6 +569,40 @@ class _RenderTextSelectionToolbarItemsLayout extends RenderBox with ContainerRen size = nextSize; } + // Horizontally expand the children when the menu overflows so they can react to + // pointer events into their whole area. + void _resizeChildrenWhenOverflow() { + if (!overflowOpen) { + return; + } + + final RenderBox navButton = firstChild!; + int i = -1; + + visitChildren((RenderObject renderObjectChild) { + final RenderBox child = renderObjectChild as RenderBox; + final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData; + + i++; + + // Ignore the navigation button. + if (renderObjectChild == navButton) { + return; + } + + // There is no need to update children that won't be painted. + if (!_shouldPaintChild(renderObjectChild, i)) { + childParentData.shouldPaint = false; + return; + } + + child.layout( + BoxConstraints.tightFor(width: size.width), + parentUsesSize: true, + ); + }); + } + @override void performLayout() { _lastIndexThatFits = -1; @@ -579,6 +613,7 @@ class _RenderTextSelectionToolbarItemsLayout extends RenderBox with ContainerRen _layoutChildren(); _placeChildren(); + _resizeChildrenWhenOverflow(); } @override diff --git a/packages/flutter/test/material/text_selection_toolbar_test.dart b/packages/flutter/test/material/text_selection_toolbar_test.dart index 244942270d0..02a695058a2 100644 --- a/packages/flutter/test/material/text_selection_toolbar_test.dart +++ b/packages/flutter/test/material/text_selection_toolbar_test.dart @@ -291,4 +291,64 @@ void main() { ); }); } + + testWidgets('Overflowed menu expands children horizontally', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/144089. + late StateSetter setState; + final List children = List.generate(7, (int i) => const TestBox()); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setter) { + setState = setter; + return TextSelectionToolbar( + anchorAbove: const Offset(50.0, 100.0), + anchorBelow: const Offset(50.0, 200.0), + children: children, + ); + }, + ), + ), + ), + ); + + // All children fit on the screen, so they are all rendered. + expect(find.byType(TestBox), findsNWidgets(children.length)); + expect(findOverflowButton(), findsNothing); + + const String short = 'Short'; + const String medium = 'Medium length'; + const String long = 'Long label in the overflow menu'; + + // Adding several children makes the menu overflow. + setState(() { + children.addAll(const [ + Text(short), + Text(medium), + Text(long), + ]); + }); + await tester.pumpAndSettle(); + expect(findOverflowButton(), findsOneWidget); + + // Tap the overflow button to show the overflow menu. + await tester.tap(findOverflowButton()); + await tester.pumpAndSettle(); + expect(find.byType(TestBox), findsNothing); + expect(find.byType(Text), findsNWidgets(3)); + expect(findOverflowButton(), findsOneWidget); + + Finder findToolbarContainer() { + return find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionToolbarContainer'); + } + expect(findToolbarContainer(), findsAtLeastNWidgets(1)); + + // Buttons have their width set to the container width. + final double overflowMenuWidth = tester.getRect(findToolbarContainer()).width; + expect(tester.getRect(find.text(long)).width, overflowMenuWidth); + expect(tester.getRect(find.text(medium)).width, overflowMenuWidth); + expect(tester.getRect(find.text(short)).width, overflowMenuWidth); + }); }