Unverified Commit 7675a6ea authored by Mouad Debbar's avatar Mouad Debbar Committed by GitHub

Add support for text selection via mouse to Cupertino text fields (#29769)

parent bfa1d25b
...@@ -533,6 +533,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -533,6 +533,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
_editableTextKey.currentState.showToolbar(); _editableTextKey.currentState.showToolbar();
} }
void _handleMouseDragSelectionStart(DragStartDetails details) {
_renderEditable.selectPositionAt(
from: details.globalPosition,
cause: SelectionChangedCause.drag,
);
}
void _handleMouseDragSelectionUpdate(
DragStartDetails startDetails,
DragUpdateDetails updateDetails,
) {
_renderEditable.selectPositionAt(
from: startDetails.globalPosition,
to: updateDetails.globalPosition,
cause: SelectionChangedCause.drag,
);
}
void _handleMouseDragSelectionEnd(DragEndDetails details) {
_requestKeyboard();
}
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) { void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
if (cause == SelectionChangedCause.longPress) { if (cause == SelectionChangedCause.longPress) {
_editableTextKey.currentState?.bringIntoView(selection.base); _editableTextKey.currentState?.bringIntoView(selection.base);
...@@ -742,6 +764,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -742,6 +764,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate, onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd, onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown, onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: _addTextDependentAttachments(paddedEditable, textStyle), child: _addTextDependentAttachments(paddedEditable, textStyle),
), ),
......
...@@ -743,7 +743,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -743,7 +743,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
} }
} }
void _handleDragSelectionStart(DragStartDetails details) { void _handleMouseDragSelectionStart(DragStartDetails details) {
_renderEditable.selectPositionAt( _renderEditable.selectPositionAt(
from: details.globalPosition, from: details.globalPosition,
cause: SelectionChangedCause.drag, cause: SelectionChangedCause.drag,
...@@ -751,7 +751,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -751,7 +751,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_startSplash(details.globalPosition); _startSplash(details.globalPosition);
} }
void _handleDragSelectionUpdate( void _handleMouseDragSelectionUpdate(
DragStartDetails startDetails, DragStartDetails startDetails,
DragUpdateDetails updateDetails, DragUpdateDetails updateDetails,
) { ) {
...@@ -930,8 +930,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -930,8 +930,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate, onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd, onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown, onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleDragSelectionStart, onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleDragSelectionUpdate, onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: child, child: child,
), ),
......
...@@ -8,7 +8,7 @@ import 'package:flutter/cupertino.dart'; ...@@ -8,7 +8,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/gestures.dart' show DragStartBehavior, PointerDeviceKind;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class MockClipboard { class MockClipboard {
...@@ -1791,6 +1791,99 @@ void main() { ...@@ -1791,6 +1791,99 @@ void main() {
expect(controller.selection.extentOffset, 5); expect(controller.selection.extentOffset, 5);
}); });
testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(
CupertinoApp(
home: Center(
child: CupertinoTextField(
dragStartBehavior: DragStartBehavior.down,
controller: controller,
style: const TextStyle(
fontFamily: 'Ahem',
fontSize: 10.0,
),
),
),
),
);
const String testValue = 'abc def ghi';
await tester.enterText(find.byType(CupertinoTextField), testValue);
// Skip past scrolling animation.
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
final Offset gPos = textOffsetToPosition(tester, testValue.indexOf('g'));
final TestGesture gesture = await tester.startGesture(ePos, kind: PointerDeviceKind.mouse);
await tester.pump();
await gesture.moveTo(gPos);
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, testValue.indexOf('e'));
expect(controller.selection.extentOffset, testValue.indexOf('g'));
});
testWidgets('Continuous dragging does not cause flickering', (WidgetTester tester) async {
int selectionChangedCount = 0;
const String testValue = 'abc def ghi';
final TextEditingController controller = TextEditingController(text: testValue);
controller.addListener(() {
selectionChangedCount++;
});
await tester.pumpWidget(
CupertinoApp(
home: Center(
child: CupertinoTextField(
dragStartBehavior: DragStartBehavior.down,
controller: controller,
style: const TextStyle(
fontFamily: 'Ahem',
fontSize: 10.0,
),
),
),
),
);
final Offset cPos = textOffsetToPosition(tester, 2); // Index of 'c'.
final Offset gPos = textOffsetToPosition(tester, 8); // Index of 'g'.
final Offset hPos = textOffsetToPosition(tester, 9); // Index of 'h'.
// Drag from 'c' to 'g'.
final TestGesture gesture = await tester.startGesture(cPos, kind: PointerDeviceKind.mouse);
await tester.pump();
await gesture.moveTo(gPos);
await tester.pumpAndSettle();
expect(selectionChangedCount, isNonZero);
selectionChangedCount = 0;
expect(controller.selection.baseOffset, 2);
expect(controller.selection.extentOffset, 8);
// Tiny movement shouldn't cause text selection to change.
await gesture.moveTo(gPos + const Offset(4.0, 0.0));
await tester.pumpAndSettle();
expect(selectionChangedCount, 0);
// Now a text selection change will occur after a significant movement.
await gesture.moveTo(hPos);
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(selectionChangedCount, 1);
expect(controller.selection.baseOffset, 2);
expect(controller.selection.extentOffset, 9);
});
testWidgets( testWidgets(
'text field respects theme', 'text field respects theme',
(WidgetTester tester) async { (WidgetTester tester) async {
......
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