Fix line breaks being lost when copying after selection gesture in SelectableRegion (#184421)
Original discussion: https://github.com/flutter/flutter/pull/184043
Fixes #154253
Before this change the `_rect` used by `_SelectableFragment` was
calculated using `getBoxesForSelection` which defaults to a
`BoxHeightStyle` of `BoxHeightStyle.tight`. This style ensures the boxes
returned from `getBoxesForSelection` tightly wrap each character. This
was causing an issue when text ended or was split on new lines because
the `SelectableFragment._rect` did not include the region that actually
had the new lines. So when `_SelectableFragment` internally used
`SelectionUtils.adjustDragOffset` to clamp the position to the
selectable rect, the selectable rect is inaccurate and always results in
a position that is above the new line and not on it.
After this change the `_rect` used by `_SelectableFragment` is
calculated using `getBoxesForSelection` with a `BoxHeightStyle` of
`BoxHeightStyle.max`. This style ensures the boxes returned from
`getBoxesForSelection` cover the entire height of the line, and as a
result including the new line character region.
Other notes:
`TextPainter.getBoxesForSelection` explicitly notes that leading or
trailing new line characters will be represented by zero-width boxes.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
---------
Co-authored-by: Renzo Olivares <roliv@google.com>
diff --git a/examples/api/test/material/selection_area/selection_area.2_test.dart b/examples/api/test/material/selection_area/selection_area.2_test.dart
index 91ed4ac..d97c35b 100644
--- a/examples/api/test/material/selection_area/selection_area.2_test.dart
+++ b/examples/api/test/material/selection_area/selection_area.2_test.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -9,6 +10,17 @@
as example;
import 'package:flutter_test/flutter_test.dart';
+// This was taken directly from selectable_region_test.dart
+// in the frameworks' Widget library tests.
+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) +
+ Offset(0.0, paragraph.preferredLineHeight);
+ return paragraph.localToGlobal(localOffset) +
+ const Offset(kIsWeb ? 1.0 : 0.0, -2.0);
+}
+
void main() {
testWidgets('SelectionArea Color Text Red Example Smoke Test', (
WidgetTester tester,
@@ -80,14 +92,12 @@
);
// Drag to select from paragraph 1 position 4 to paragraph 3 position 25.
final TestGesture gesture = await tester.startGesture(
- tester.getRect(paragraph1Finder).topLeft + const Offset(50.0, 10.0),
+ textOffsetToPosition(paragraph1, 4),
kind: PointerDeviceKind.mouse,
);
addTearDown(gesture.removePointer);
await tester.pump();
- await gesture.moveTo(
- tester.getRect(paragraph3Finder).centerLeft + const Offset(360.0, 0.0),
- );
+ await gesture.moveTo(textOffsetToPosition(paragraph3, 25));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
@@ -97,7 +107,7 @@
expect(paragraph1.selections.length, 1);
expect(
paragraph1.selections[0],
- const TextSelection(baseOffset: 4, extentOffset: 27),
+ const TextSelection(baseOffset: 4, extentOffset: 28),
);
// Bulleted list.
for (final RenderParagraph paragraphBullet in bullets) {
@@ -263,7 +273,7 @@
((paragraph3ResultingSpan.children![0] as TextSpan).children![0]
as TextSpan)
.text,
- 'This is some text in ano',
+ 'This is some text in anot',
);
expect(
(paragraph3ResultingSpan.children![0] as TextSpan).children![0].style,