diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 81f4ee9ac0b46..41df07cb578ab 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -701,6 +701,12 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin _textPainter.preferredLineHeight; + double _computeIntrinsicHeight(double width) { return (_textIntrinsics ..setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline)) @@ -2997,6 +3003,7 @@ class _SelectableFragment with Selectable, Diagnosticable, ChangeNotifier implem if (_cachedBoundingBoxes == null) { final List boxes = paragraph.getBoxesForSelection( TextSelection(baseOffset: range.start, extentOffset: range.end), + boxHeightStyle: ui.BoxHeightStyle.max, ); if (boxes.isNotEmpty) { _cachedBoundingBoxes = []; @@ -3034,6 +3041,7 @@ class _SelectableFragment with Selectable, Diagnosticable, ChangeNotifier implem void didChangeParagraphLayout() { _cachedRect = null; + _cachedBoundingBoxes = null; } @override diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index 051ddf2d6d4a7..a38bd014b7e1c 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -16,8 +16,8 @@ import 'semantics_tester.dart'; Offset textOffsetToPosition(RenderParagraph paragraph, int offset) { const Rect caret = Rect.fromLTWH(0.0, 0.0, 2.0, 20.0); - final Offset localOffset = paragraph.getOffsetForCaret(TextPosition(offset: offset), caret); - return paragraph.localToGlobal(localOffset); + final Offset localOffset = paragraph.getOffsetForCaret(TextPosition(offset: offset), caret) + Offset(0.0, paragraph.preferredLineHeight); + return paragraph.localToGlobal(localOffset) + const Offset(kIsWeb ? 1.0 : 0.0, -2.0); } Offset globalize(Offset point, RenderBox box) { @@ -1316,6 +1316,68 @@ void main() { skip: kIsWeb, // https://github.com/flutter/flutter/issues/125582. ); + testWidgets('RenderParagraph should invalidate cached bounding boxes', (WidgetTester tester) async { + final UniqueKey outerText = UniqueKey(); + final FocusNode focusNode = FocusNode(); + addTearDown(focusNode.dispose); + addTearDown(tester.view.reset); + + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: materialTextSelectionControls, + child: Scaffold( + body: Center( + child: Text( + 'How are you doing today? Good, and you?', + key: outerText, + ), + ), + ), + ), + ), + ); + final RenderParagraph paragraph = tester.renderObject(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first); + final SelectableRegionState state = + tester.state(find.byType(SelectableRegion)); + + // Double click to select word at position. + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 27), kind: PointerDeviceKind.mouse); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.up(); + await tester.pump(); + await gesture.down(textOffsetToPosition(paragraph, 27)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + // Should select "Good". + expect(paragraph.selections[0], const TextSelection(baseOffset: 25, extentOffset: 29)); + + // Change the size of the window. + tester.view.physicalSize = const Size(800.0, 400.0); + await tester.pumpAndSettle(); + state.clearSelection(); + await tester.pumpAndSettle(kDoubleTapTimeout); + expect(paragraph.selections.isEmpty, isTrue); + + // Double click at the same position. + await gesture.down(textOffsetToPosition(paragraph, 27)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + await gesture.down(textOffsetToPosition(paragraph, 27)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + // Should select "Good" again. + expect(paragraph.selections.isEmpty, isFalse); + expect(paragraph.selections[0], const TextSelection(baseOffset: 25, extentOffset: 29)); + }, skip: kIsWeb); // https://github.com/flutter/flutter/issues/125582. + testWidgets('mouse can select single text on desktop platforms', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); addTearDown(focusNode.dispose); @@ -3398,12 +3460,8 @@ void main() { ); final RenderParagraph paragraph = tester.renderObject(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first); - // Adjust `textOffsetToPosition` result because it returns the wrong vertical position (wrong line). - // TODO(bleroux): Remove when https://github.com/flutter/flutter/issues/133637 is fixed. - final Offset gestureOffset = textOffsetToPosition(paragraph, 125).translate(0, 10); - // Right click to select word at position. - final TestGesture gesture = await tester.startGesture(gestureOffset, kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 125), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); addTearDown(gesture.removePointer); await tester.pump(); await gesture.up(); @@ -3448,12 +3506,8 @@ void main() { ); final RenderParagraph paragraph = tester.renderObject(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first); - // Adjust `textOffsetToPosition` result because it returns the wrong vertical position (wrong line). - // TODO(bleroux): Remove when https://github.com/flutter/flutter/issues/133637 is fixed. - final Offset gestureOffset = textOffsetToPosition(paragraph, 125).translate(0, 10); - // Right click to select word at position. - final TestGesture gesture = await tester.startGesture(gestureOffset, kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 125), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); addTearDown(gesture.removePointer); await tester.pump(); await gesture.up();