Unverified Commit 29397c2c authored by Renzo Olivares's avatar Renzo Olivares Committed by GitHub

Fix selectWordsInRange when last word is located before the first word (#113224)

parent bd9021a0
...@@ -2066,7 +2066,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2066,7 +2066,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
selectWordsInRange(from: _lastTapDownPosition!, cause: cause); selectWordsInRange(from: _lastTapDownPosition!, cause: cause);
} }
/// Selects the set words of a paragraph in a given range of global positions. /// Selects the set words of a paragraph that intersect a given range of global positions.
///
/// The set of words selected are not strictly bounded by the range of global positions.
/// ///
/// The first and last endpoints of the selection will always be at the /// The first and last endpoints of the selection will always be at the
/// beginning and end of a word respectively. /// beginning and end of a word respectively.
...@@ -2076,15 +2078,17 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2076,15 +2078,17 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
assert(cause != null); assert(cause != null);
assert(from != null); assert(from != null);
_computeTextMetricsIfNeeded(); _computeTextMetricsIfNeeded();
final TextPosition firstPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset)); final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
final TextSelection firstWord = _getWordAtOffset(firstPosition); final TextSelection fromWord = _getWordAtOffset(fromPosition);
final TextSelection lastWord = to == null ? final TextPosition toPosition = to == null ? fromPosition : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset));
firstWord : _getWordAtOffset(_textPainter.getPositionForOffset(globalToLocal(to - _paintOffset))); final TextSelection toWord = toPosition == fromPosition ? fromWord : _getWordAtOffset(toPosition);
final bool isFromWordBeforeToWord = fromWord.start < toWord.end;
_setSelection( _setSelection(
TextSelection( TextSelection(
baseOffset: firstWord.base.offset, baseOffset: isFromWordBeforeToWord ? fromWord.base.offset : fromWord.extent.offset,
extentOffset: lastWord.extent.offset, extentOffset: isFromWordBeforeToWord ? toWord.extent.offset : toWord.base.offset,
affinity: firstWord.affinity, affinity: fromWord.affinity,
), ),
cause, cause,
); );
......
...@@ -8432,6 +8432,84 @@ void main() { ...@@ -8432,6 +8432,84 @@ void main() {
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }), variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
); );
testWidgets(
'long press drag extends the selection to the word under the drag and shows toolbar on lift on non-Apple platforms',
(WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: TextField(
controller: controller,
),
),
),
),
);
final TestGesture gesture =
await tester.startGesture(textOffsetToPosition(tester, 18));
await tester.pump(const Duration(milliseconds: 500));
// Long press selects the word at the long presses position.
expect(
controller.selection,
const TextSelection(baseOffset: 13, extentOffset: 23),
);
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(TextButton), findsNothing);
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 13, extentOffset: 35),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
// The selection is moved on a backwards drag.
await gesture.moveBy(const Offset(-200, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 8),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
await gesture.moveBy(const Offset(-100, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
await gesture.up();
await tester.pumpAndSettle();
// The selection isn't affected by the gesture lift.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// The toolbar now shows up.
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
testWidgets( testWidgets(
'long press drag moves the cursor under the drag and shows toolbar on lift', 'long press drag moves the cursor under the drag and shows toolbar on lift',
(WidgetTester tester) async { (WidgetTester tester) async {
......
...@@ -3372,7 +3372,7 @@ void main() { ...@@ -3372,7 +3372,7 @@ void main() {
); );
testWidgets( testWidgets(
'long press drag moves the cursor under the drag and shows toolbar on lift (iOS)', 'long press drag extends the selection to the word under the drag and shows toolbar on lift on non-Apple platforms',
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
...@@ -3384,10 +3384,84 @@ void main() { ...@@ -3384,10 +3384,84 @@ void main() {
), ),
); );
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText)); final TestGesture gesture =
await tester.startGesture(textOffsetToPosition(tester, 18));
await tester.pump(const Duration(milliseconds: 500));
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// Long press selects the word at the long presses position.
expect(
controller.selection,
const TextSelection(baseOffset: 13, extentOffset: 23),
);
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(TextButton), findsNothing);
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 13, extentOffset: 35),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
// The selection is moved on a backwards drag.
await gesture.moveBy(const Offset(-200, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 8),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
await gesture.moveBy(const Offset(-100, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
await gesture.up();
await tester.pumpAndSettle();
// The selection isn't affected by the gesture lift.
expect(
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// The toolbar now shows up.
expect(find.byType(TextButton), findsNWidgets(2));
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
testWidgets(
'long press drag extends the selection to the word under the drag and shows toolbar on lift (iOS)',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: Center(
child: SelectableText('Atwater Peel Sherbrooke Bonaventure'),
),
),
),
);
final TestGesture gesture = final TestGesture gesture =
await tester.startGesture(selectableTextStart + const Offset(50.0, 5.0)); await tester.startGesture(textOffsetToPosition(tester, 18));
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first); final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
...@@ -3397,36 +3471,52 @@ void main() { ...@@ -3397,36 +3471,52 @@ void main() {
expect( expect(
controller.selection, controller.selection,
const TextSelection( const TextSelection(
baseOffset: 0, baseOffset: 13,
extentOffset: 7, extentOffset: 23,
), ),
); );
// Cursor move doesn't trigger a toolbar initially. // Word select doesn't trigger a toolbar initially.
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.moveBy(const Offset(100, 0)); await gesture.moveBy(const Offset(100, 0));
await tester.pump(); await tester.pump();
// The selection position is now moved with the drag. // The selection is now moved with the drag.
expect( expect(
controller.selection, controller.selection,
const TextSelection( const TextSelection(
baseOffset: 0, baseOffset: 13,
extentOffset: 12, extentOffset: 35,
), ),
); );
// Still no toolbar. // Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.moveBy(const Offset(100, 0)); // The selection is moved with a backwards drag.
await gesture.moveBy(const Offset(-200, 0));
await tester.pump(); await tester.pump();
// The selection position is now moved with the drag. // The selection is now moved with the drag.
expect( expect(
controller.selection, controller.selection,
const TextSelection( const TextSelection(
baseOffset: 0, baseOffset: 23,
extentOffset: 23, extentOffset: 8,
),
);
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing);
// The selection is moved with a backwards drag.
await gesture.moveBy(const Offset(-100, 0));
await tester.pump();
// The selection is now moved with the drag.
expect(
controller.selection,
const TextSelection(
baseOffset: 23,
extentOffset: 0,
), ),
); );
// Still no toolbar. // Still no toolbar.
...@@ -3439,8 +3529,8 @@ void main() { ...@@ -3439,8 +3529,8 @@ void main() {
expect( expect(
controller.selection, controller.selection,
const TextSelection( const TextSelection(
baseOffset: 0, baseOffset: 23,
extentOffset: 23, extentOffset: 0,
), ),
); );
// The toolbar now shows up. // The toolbar now shows up.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment