From 8a312cd01a02fd0eb9617ba2c6e8f4680b561abb Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 1 Mar 2024 07:40:47 +0100 Subject: [PATCH] Horizontally expand text selection toolbar buttons in overflow menu (#144391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR expands the items displayed in the overflow menu of a `TextSelectionToolbar` making buttons clickable in the blank area. | Before | After | |--------|--------| | Each item has its own width | All items expand horizontally | | ![Capture d’écran 2024-02-29 à 14 43 57](https://github.com/flutter/flutter/assets/840911/f7379eef-9185-4cc4-bf14-e4c916c432b1) | ![Capture d’écran 2024-02-29 à 14 40 47](https://github.com/flutter/flutter/assets/840911/bff272cd-9fe2-4f07-adaf-61edef03d26e) | ## Related Issue Fixes https://github.com/flutter/flutter/issues/144089. ## Tests Adds 1 tests. --- .../adaptive_text_selection_toolbar.dart | 1 + .../lib/src/material/text_selection.dart | 1 + .../src/material/text_selection_toolbar.dart | 35 +++++++++++ .../material/text_selection_toolbar_test.dart | 60 +++++++++++++++++++ 4 files changed, 97 insertions(+) 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); + }); }