mirror of
https://github.com/flutter/flutter
synced 2024-10-02 22:44:13 +00:00
Text editing shift + tap + drag interaction (#95213)
Supports the desktop text editing interaction of holding shift, tapping the field, and dragging to modify the selection.
This commit is contained in:
parent
45f8c39052
commit
734c3c4f8d
|
@ -1026,9 +1026,23 @@ class TextSelectionGestureDetectorBuilder {
|
|||
// The viewport offset pixels of the [RenderEditable] at the last drag start.
|
||||
double _dragStartViewportOffset = 0.0;
|
||||
|
||||
// Returns true iff either shift key is currently down.
|
||||
bool get _isShiftPressed {
|
||||
return HardwareKeyboard.instance.logicalKeysPressed
|
||||
.any(<LogicalKeyboardKey>{
|
||||
LogicalKeyboardKey.shiftLeft,
|
||||
LogicalKeyboardKey.shiftRight,
|
||||
}.contains);
|
||||
}
|
||||
|
||||
// True iff a tap + shift has been detected but the tap has not yet come up.
|
||||
bool _isShiftTapping = false;
|
||||
|
||||
// For a shift + tap + drag gesture, the TextSelection at the point of the
|
||||
// tap. Mac uses this value to reset to the original selection when an
|
||||
// inversion of the base and offset happens.
|
||||
TextSelection? _shiftTapDragSelection;
|
||||
|
||||
/// Handler for [TextSelectionGestureDetector.onTapDown].
|
||||
///
|
||||
/// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
|
||||
|
@ -1050,12 +1064,7 @@ class TextSelectionGestureDetectorBuilder {
|
|||
|| kind == PointerDeviceKind.stylus;
|
||||
|
||||
// Handle shift + click selection if needed.
|
||||
final bool isShiftPressed = HardwareKeyboard.instance.logicalKeysPressed
|
||||
.any(<LogicalKeyboardKey>{
|
||||
LogicalKeyboardKey.shiftLeft,
|
||||
LogicalKeyboardKey.shiftRight,
|
||||
}.contains);
|
||||
if (isShiftPressed && renderEditable.selection?.baseOffset != null) {
|
||||
if (_isShiftPressed && renderEditable.selection?.baseOffset != null) {
|
||||
_isShiftTapping = true;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
|
@ -1290,10 +1299,27 @@ class TextSelectionGestureDetectorBuilder {
|
|||
|| kind == PointerDeviceKind.touch
|
||||
|| kind == PointerDeviceKind.stylus;
|
||||
|
||||
renderEditable.selectPositionAt(
|
||||
from: details.globalPosition,
|
||||
cause: SelectionChangedCause.drag,
|
||||
);
|
||||
if (_isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) {
|
||||
_isShiftTapping = true;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
_expandSelection(details.globalPosition, SelectionChangedCause.drag);
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
_extendSelection(details.globalPosition, SelectionChangedCause.drag);
|
||||
break;
|
||||
}
|
||||
_shiftTapDragSelection = renderEditable.selection;
|
||||
} else {
|
||||
renderEditable.selectPositionAt(
|
||||
from: details.globalPosition,
|
||||
cause: SelectionChangedCause.drag,
|
||||
);
|
||||
}
|
||||
|
||||
_dragStartViewportOffset = renderEditable.offset.pixels;
|
||||
}
|
||||
|
@ -1312,28 +1338,77 @@ class TextSelectionGestureDetectorBuilder {
|
|||
if (!delegate.selectionEnabled)
|
||||
return;
|
||||
|
||||
// Adjust the drag start offset for possible viewport offset changes.
|
||||
final Offset startOffset = renderEditable.maxLines == 1
|
||||
? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
|
||||
: Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset);
|
||||
if (!_isShiftTapping) {
|
||||
// Adjust the drag start offset for possible viewport offset changes.
|
||||
final Offset startOffset = renderEditable.maxLines == 1
|
||||
? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
|
||||
: Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset);
|
||||
|
||||
renderEditable.selectPositionAt(
|
||||
from: startDetails.globalPosition - startOffset,
|
||||
to: updateDetails.globalPosition,
|
||||
cause: SelectionChangedCause.drag,
|
||||
);
|
||||
return renderEditable.selectPositionAt(
|
||||
from: startDetails.globalPosition - startOffset,
|
||||
to: updateDetails.globalPosition,
|
||||
cause: SelectionChangedCause.drag,
|
||||
);
|
||||
}
|
||||
|
||||
if (_shiftTapDragSelection!.isCollapsed
|
||||
|| (defaultTargetPlatform != TargetPlatform.iOS
|
||||
&& defaultTargetPlatform != TargetPlatform.macOS)) {
|
||||
return _extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag);
|
||||
}
|
||||
|
||||
// If the drag inverts the selection, Mac and iOS revert to the initial
|
||||
// selection.
|
||||
final TextSelection selection = editableText.textEditingValue.selection;
|
||||
final TextPosition nextExtent = renderEditable.getPositionForPoint(updateDetails.globalPosition);
|
||||
final bool isShiftTapDragSelectionForward =
|
||||
_shiftTapDragSelection!.baseOffset < _shiftTapDragSelection!.extentOffset;
|
||||
final bool isInverted = isShiftTapDragSelectionForward
|
||||
? nextExtent.offset < _shiftTapDragSelection!.baseOffset
|
||||
: nextExtent.offset > _shiftTapDragSelection!.baseOffset;
|
||||
if (isInverted && selection.baseOffset == _shiftTapDragSelection!.baseOffset) {
|
||||
editableText.userUpdateTextEditingValue(
|
||||
editableText.textEditingValue.copyWith(
|
||||
selection: TextSelection(
|
||||
baseOffset: _shiftTapDragSelection!.extentOffset,
|
||||
extentOffset: nextExtent.offset,
|
||||
),
|
||||
),
|
||||
SelectionChangedCause.drag,
|
||||
);
|
||||
} else if (!isInverted
|
||||
&& nextExtent.offset != _shiftTapDragSelection!.baseOffset
|
||||
&& selection.baseOffset != _shiftTapDragSelection!.baseOffset) {
|
||||
editableText.userUpdateTextEditingValue(
|
||||
editableText.textEditingValue.copyWith(
|
||||
selection: TextSelection(
|
||||
baseOffset: _shiftTapDragSelection!.baseOffset,
|
||||
extentOffset: nextExtent.offset,
|
||||
),
|
||||
),
|
||||
SelectionChangedCause.drag,
|
||||
);
|
||||
} else {
|
||||
_extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for [TextSelectionGestureDetector.onDragSelectionEnd].
|
||||
///
|
||||
/// By default, it services as place holder to enable subclass override.
|
||||
/// By default, it simply cleans up the state used for handling certain
|
||||
/// built-in behaviors.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TextSelectionGestureDetector.onDragSelectionEnd], which triggers this
|
||||
/// callback.
|
||||
@protected
|
||||
void onDragSelectionEnd(DragEndDetails details) {/* Subclass should override this method if needed. */}
|
||||
void onDragSelectionEnd(DragEndDetails details) {
|
||||
if (_isShiftTapping) {
|
||||
_isShiftTapping = false;
|
||||
_shiftTapDragSelection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [TextSelectionGestureDetector] configured with the handlers
|
||||
/// provided by this builder.
|
||||
|
|
|
@ -5052,4 +5052,392 @@ void main() {
|
|||
expect(controller.selection.baseOffset, 13);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard (Apple platforms)', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 23),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 28));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 28);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Invert the selection. The base jumps to the original extent.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 7));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 7);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 4));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Continue to move past the original base, which will cause the selection
|
||||
// to invert back to the original orientation.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 9));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 9);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 26));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard (non-Apple platforms)', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 23),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 28));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 28);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Invert the selection. The original selection is not restored like on iOS
|
||||
// and Mac.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 7));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 7);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 4));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Continue to move past the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 9));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 9);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 26));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard, reversed (Apple platforms)', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Make a selection from right to left.
|
||||
await tester.tapAt(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 8),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 5);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Invert the selection. The base jumps to the original extent.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 27));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 27);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Continue to move past the original base, which will cause the selection
|
||||
// to invert back to the original orientation.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 22));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 22);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 16));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 14));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard, reversed (non-Apple platforms)', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Make a selection from right to left.
|
||||
await tester.tapAt(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 8),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 5);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Invert the selection. The selection is not restored like it would be on
|
||||
// iOS and Mac.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 27));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 27);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Continue to move past the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 22));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 22);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 16));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 14));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }));
|
||||
}
|
||||
|
|
|
@ -10557,4 +10557,392 @@ void main() {
|
|||
expect(controller.selection.baseOffset, 13);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard (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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 23),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 28));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 28);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Invert the selection. The base jumps to the original extent.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 7));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 7);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 4));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Continue to move past the original base, which will cause the selection
|
||||
// to invert back to the original orientation.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 9));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 9);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 26));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard (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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 23),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 28));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 28);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Invert the selection. The original selection is not restored like on iOS
|
||||
// and Mac.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 7));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 7);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 4));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Continue to move past the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 9));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 9);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 26));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 26);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard, reversed (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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Make a selection from right to left.
|
||||
await tester.tapAt(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 8),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 5);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Invert the selection. The base jumps to the original extent.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 27));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 27);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 8);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Continue to move past the original base, which will cause the selection
|
||||
// to invert back to the original orientation.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 22));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 22);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 16));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 14));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('can shift + tap + drag to select with a keyboard, reversed (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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Make a selection from right to left.
|
||||
await tester.tapAt(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
await tester.pump(kDoubleTapTimeout);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
final TestGesture gesture =
|
||||
await tester.startGesture(
|
||||
textOffsetToPosition(tester, 8),
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Expand the selection a bit.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 5);
|
||||
|
||||
// Move back to the original selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 8));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 8);
|
||||
|
||||
// Collapse the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Invert the selection. The selection is not restored like it would be on
|
||||
// iOS and Mac.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 24));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 24);
|
||||
|
||||
// Continuing to move in the inverted direction expands the selection.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 27));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 27);
|
||||
|
||||
// Move back to the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 23));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 23);
|
||||
|
||||
// Continue to move past the original base.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 22));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 22);
|
||||
|
||||
// Continuing to select in this direction selects just like it did
|
||||
// originally.
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 16));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
|
||||
// Releasing the shift key has no effect; the selection continues as the
|
||||
// mouse continues to move.
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 16);
|
||||
await gesture.moveTo(textOffsetToPosition(tester, 14));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
|
||||
await gesture.up();
|
||||
expect(controller.selection.baseOffset, 23);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue