Skip to content

Commit 715fcaa

Browse files
authored
Can't drag the cursor with the mouse (#103002)
1 parent 8afaf7b commit 715fcaa

File tree

6 files changed

+254
-12
lines changed

6 files changed

+254
-12
lines changed

packages/flutter/lib/src/widgets/text_selection.dart

+21-5
Original file line numberDiff line numberDiff line change
@@ -1208,12 +1208,28 @@ class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> with S
12081208
alignment: Alignment.topLeft,
12091209
width: interactiveRect.width,
12101210
height: interactiveRect.height,
1211-
child: GestureDetector(
1211+
child: RawGestureDetector(
12121212
behavior: HitTestBehavior.translucent,
1213-
dragStartBehavior: widget.dragStartBehavior,
1214-
onPanStart: widget.onSelectionHandleDragStart,
1215-
onPanUpdate: widget.onSelectionHandleDragUpdate,
1216-
onPanEnd: widget.onSelectionHandleDragEnd,
1213+
gestures: <Type, GestureRecognizerFactory>{
1214+
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
1215+
() => PanGestureRecognizer(
1216+
debugOwner: this,
1217+
// Mouse events select the text and do not drag the cursor.
1218+
supportedDevices: <PointerDeviceKind>{
1219+
PointerDeviceKind.touch,
1220+
PointerDeviceKind.stylus,
1221+
PointerDeviceKind.unknown,
1222+
},
1223+
),
1224+
(PanGestureRecognizer instance) {
1225+
instance
1226+
..dragStartBehavior = widget.dragStartBehavior
1227+
..onStart = widget.onSelectionHandleDragStart
1228+
..onUpdate = widget.onSelectionHandleDragUpdate
1229+
..onEnd = widget.onSelectionHandleDragEnd;
1230+
},
1231+
),
1232+
},
12171233
child: Padding(
12181234
padding: EdgeInsets.only(
12191235
left: padding.left,

packages/flutter/test/cupertino/text_field_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -2897,7 +2897,7 @@ void main() {
28972897
// The selection doesn't move beyond the left handle. There's always at
28982898
// least 1 char selected.
28992899
expect(controller.selection.extentOffset, 5);
2900-
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
2900+
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
29012901

29022902
testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async {
29032903
final TextEditingController controller = TextEditingController();
@@ -3571,7 +3571,7 @@ void main() {
35713571

35723572
expect(left.opacity.value, equals(1.0));
35733573
expect(right.opacity.value, equals(1.0));
3574-
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
3574+
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
35753575

35763576
testWidgets('when CupertinoTextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async {
35773577
final ScrollController scrollController = ScrollController();

packages/flutter/test/material/text_field_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -9400,7 +9400,7 @@ void main() {
94009400

94019401
expect(left.opacity.value, equals(1.0));
94029402
expect(right.opacity.value, equals(1.0));
9403-
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
9403+
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
94049404

94059405
testWidgets('iPad Scribble selection change shows selection handles', (WidgetTester tester) async {
94069406
const String testText = 'lorem ipsum';

packages/flutter/test/widgets/editable_text_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -7825,7 +7825,7 @@ void main() {
78257825
// On web, we don't show the Flutter toolbar and instead rely on the browser
78267826
// toolbar. Until we change that, this test should remain skipped.
78277827
skip: kIsWeb, // [intended]
7828-
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })
7828+
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })
78297829
);
78307830

78317831
testWidgets("scrolling doesn't bounce", (WidgetTester tester) async {

packages/flutter/test/widgets/selectable_text_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -4279,7 +4279,7 @@ void main() {
42794279

42804280
expect(left.opacity.value, equals(1.0));
42814281
expect(right.opacity.value, equals(1.0));
4282-
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
4282+
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
42834283

42844284
testWidgets('Long press shows handles and toolbar', (WidgetTester tester) async {
42854285
await tester.pumpWidget(

packages/flutter/test/widgets/text_selection_test.dart

+228-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
1010
import 'package:flutter_test/flutter_test.dart';
1111

1212
import 'clipboard_utils.dart';
13+
import 'editable_text_utils.dart';
1314

1415
void main() {
1516
late int tapCount;
@@ -628,7 +629,7 @@ void main() {
628629
});
629630

630631
testWidgets('Mouse drag does not show handles nor toolbar', (WidgetTester tester) async {
631-
// Regressing test for https://github.com/flutter/flutter/issues/69001
632+
// Regression test for https://github.com/flutter/flutter/issues/69001
632633
await tester.pumpWidget(
633634
const MaterialApp(
634635
home: Scaffold(
@@ -652,6 +653,231 @@ void main() {
652653
expect(editableText.selectionOverlay!.toolbarIsVisible, isFalse);
653654
});
654655

656+
testWidgets('Mouse drag selects and cannot drag cursor', (WidgetTester tester) async {
657+
// Regression test for https://github.com/flutter/flutter/issues/102928
658+
final TextEditingController controller = TextEditingController(
659+
text: 'I love flutter!',
660+
);
661+
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
662+
final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate(
663+
editableTextKey: editableTextKey,
664+
forcePressEnabled: false,
665+
selectionEnabled: true,
666+
);
667+
final TextSelectionGestureDetectorBuilder provider =
668+
TextSelectionGestureDetectorBuilder(delegate: delegate);
669+
670+
await tester.pumpWidget(
671+
MaterialApp(
672+
home: provider.buildGestureDetector(
673+
behavior: HitTestBehavior.translucent,
674+
child: EditableText(
675+
key: editableTextKey,
676+
controller: controller,
677+
focusNode: FocusNode(),
678+
backgroundCursorColor: Colors.white,
679+
cursorColor: Colors.white,
680+
style: const TextStyle(),
681+
selectionControls: materialTextSelectionControls,
682+
),
683+
),
684+
),
685+
);
686+
687+
expect(controller.selection.isCollapsed, isTrue);
688+
expect(controller.selection.baseOffset, -1);
689+
690+
final Offset position = textOffsetToPosition(tester, 4);
691+
692+
await tester.tapAt(position);
693+
await tester.pump();
694+
695+
expect(controller.selection.isCollapsed, isTrue);
696+
expect(controller.selection.baseOffset, 4);
697+
698+
final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.mouse);
699+
addTearDown(gesture.removePointer);
700+
await tester.pump();
701+
await gesture.moveTo(textOffsetToPosition(tester, 7));
702+
await tester.pump();
703+
await gesture.moveTo(textOffsetToPosition(tester, 10));
704+
await tester.pump();
705+
await gesture.up();
706+
await tester.pumpAndSettle();
707+
708+
expect(controller.selection.isCollapsed, isFalse);
709+
expect(controller.selection.baseOffset, 4);
710+
expect(controller.selection.extentOffset, 10);
711+
});
712+
713+
testWidgets('Touch drag moves the cursor', (WidgetTester tester) async {
714+
// Regression test for https://github.com/flutter/flutter/issues/102928
715+
final TextEditingController controller = TextEditingController(
716+
text: 'I love flutter!',
717+
);
718+
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
719+
final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate(
720+
editableTextKey: editableTextKey,
721+
forcePressEnabled: false,
722+
selectionEnabled: true,
723+
);
724+
final TextSelectionGestureDetectorBuilder provider =
725+
TextSelectionGestureDetectorBuilder(delegate: delegate);
726+
727+
await tester.pumpWidget(
728+
MaterialApp(
729+
home: provider.buildGestureDetector(
730+
behavior: HitTestBehavior.translucent,
731+
child: EditableText(
732+
key: editableTextKey,
733+
controller: controller,
734+
focusNode: FocusNode(),
735+
backgroundCursorColor: Colors.white,
736+
cursorColor: Colors.white,
737+
style: const TextStyle(),
738+
selectionControls: materialTextSelectionControls,
739+
),
740+
),
741+
),
742+
);
743+
744+
expect(controller.selection.isCollapsed, isTrue);
745+
expect(controller.selection.baseOffset, -1);
746+
747+
final Offset position = textOffsetToPosition(tester, 4);
748+
749+
await tester.tapAt(position);
750+
await tester.pump();
751+
752+
expect(controller.selection.isCollapsed, isTrue);
753+
expect(controller.selection.baseOffset, 4);
754+
755+
final TestGesture gesture = await tester.startGesture(position);
756+
addTearDown(gesture.removePointer);
757+
await tester.pump();
758+
await gesture.moveTo(textOffsetToPosition(tester, 7));
759+
await tester.pump();
760+
await gesture.moveTo(textOffsetToPosition(tester, 10));
761+
await tester.pump();
762+
await gesture.up();
763+
await tester.pumpAndSettle();
764+
765+
expect(controller.selection.isCollapsed, isTrue);
766+
expect(controller.selection.baseOffset, 10);
767+
});
768+
769+
testWidgets('Stylus drag moves the cursor', (WidgetTester tester) async {
770+
// Regression test for https://github.com/flutter/flutter/issues/102928
771+
final TextEditingController controller = TextEditingController(
772+
text: 'I love flutter!',
773+
);
774+
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
775+
final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate(
776+
editableTextKey: editableTextKey,
777+
forcePressEnabled: false,
778+
selectionEnabled: true,
779+
);
780+
final TextSelectionGestureDetectorBuilder provider =
781+
TextSelectionGestureDetectorBuilder(delegate: delegate);
782+
783+
await tester.pumpWidget(
784+
MaterialApp(
785+
home: provider.buildGestureDetector(
786+
behavior: HitTestBehavior.translucent,
787+
child: EditableText(
788+
key: editableTextKey,
789+
controller: controller,
790+
focusNode: FocusNode(),
791+
backgroundCursorColor: Colors.white,
792+
cursorColor: Colors.white,
793+
style: const TextStyle(),
794+
selectionControls: materialTextSelectionControls,
795+
),
796+
),
797+
),
798+
);
799+
800+
expect(controller.selection.isCollapsed, isTrue);
801+
expect(controller.selection.baseOffset, -1);
802+
803+
final Offset position = textOffsetToPosition(tester, 4);
804+
805+
await tester.tapAt(position);
806+
await tester.pump();
807+
808+
expect(controller.selection.isCollapsed, isTrue);
809+
expect(controller.selection.baseOffset, 4);
810+
811+
final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.stylus);
812+
addTearDown(gesture.removePointer);
813+
await tester.pump();
814+
await gesture.moveTo(textOffsetToPosition(tester, 7));
815+
await tester.pump();
816+
await gesture.moveTo(textOffsetToPosition(tester, 10));
817+
await tester.pump();
818+
await gesture.up();
819+
await tester.pumpAndSettle();
820+
821+
expect(controller.selection.isCollapsed, isTrue);
822+
expect(controller.selection.baseOffset, 10);
823+
});
824+
825+
testWidgets('Drag of unknown type moves the cursor', (WidgetTester tester) async {
826+
// Regression test for https://github.com/flutter/flutter/issues/102928
827+
final TextEditingController controller = TextEditingController(
828+
text: 'I love flutter!',
829+
);
830+
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
831+
final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate(
832+
editableTextKey: editableTextKey,
833+
forcePressEnabled: false,
834+
selectionEnabled: true,
835+
);
836+
final TextSelectionGestureDetectorBuilder provider =
837+
TextSelectionGestureDetectorBuilder(delegate: delegate);
838+
839+
await tester.pumpWidget(
840+
MaterialApp(
841+
home: provider.buildGestureDetector(
842+
behavior: HitTestBehavior.translucent,
843+
child: EditableText(
844+
key: editableTextKey,
845+
controller: controller,
846+
focusNode: FocusNode(),
847+
backgroundCursorColor: Colors.white,
848+
cursorColor: Colors.white,
849+
style: const TextStyle(),
850+
selectionControls: materialTextSelectionControls,
851+
),
852+
),
853+
),
854+
);
855+
856+
expect(controller.selection.isCollapsed, isTrue);
857+
expect(controller.selection.baseOffset, -1);
858+
859+
final Offset position = textOffsetToPosition(tester, 4);
860+
861+
await tester.tapAt(position);
862+
await tester.pump();
863+
864+
expect(controller.selection.isCollapsed, isTrue);
865+
expect(controller.selection.baseOffset, 4);
866+
867+
final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.unknown);
868+
addTearDown(gesture.removePointer);
869+
await tester.pump();
870+
await gesture.moveTo(textOffsetToPosition(tester, 7));
871+
await tester.pump();
872+
await gesture.moveTo(textOffsetToPosition(tester, 10));
873+
await tester.pump();
874+
await gesture.up();
875+
await tester.pumpAndSettle();
876+
877+
expect(controller.selection.isCollapsed, isTrue);
878+
expect(controller.selection.baseOffset, 10);
879+
});
880+
655881
testWidgets('test TextSelectionGestureDetectorBuilder drag with RenderEditable viewport offset change', (WidgetTester tester) async {
656882
await pumpTextSelectionGestureDetectorBuilder(tester);
657883
final FakeRenderEditable renderEditable = tester.renderObject(find.byType(FakeEditable));
@@ -767,7 +993,7 @@ void main() {
767993
of: find.byType(CompositedTransformFollower),
768994
matching: find.descendant(
769995
of: find.byType(FadeTransition),
770-
matching: find.byType(GestureDetector),
996+
matching: find.byType(RawGestureDetector),
771997
),
772998
);
773999

0 commit comments

Comments
 (0)