Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix an issue that Dragging the iOS text selection handles is jumpy and iOS text selection update incorrectly. #109136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
51a6dce
Fix an issue that Dragging the iOS text selection handles is jumpy (#…
ksballetba Aug 8, 2022
e5503cf
delete needless backspace
ksballetba Aug 8, 2022
dbca95b
Revert "delete needless backspace"
ksballetba Aug 15, 2022
cfdea60
Revert "Fix an issue that Dragging the iOS text selection handles is …
ksballetba Aug 15, 2022
56f72eb
Fix an issue that Dragging the iOS text selection update wrongly with…
ksballetba Aug 15, 2022
89bf003
make selection update more accurately
ksballetba Aug 15, 2022
fb72882
make selection update more accurately
ksballetba Aug 15, 2022
d255009
add new test to make sure cupertino text field can drag handles to ch…
ksballetba Aug 15, 2022
58f1569
clean unused local variable
ksballetba Aug 15, 2022
cb07af8
correct the test documentation.
ksballetba Aug 15, 2022
685ccaf
clean unused local variable.
ksballetba Aug 15, 2022
cb0d598
change nits.
ksballetba Aug 16, 2022
60ba360
change nits.
ksballetba Aug 16, 2022
7391863
change nits.
ksballetba Aug 16, 2022
6f3805b
change nits.
ksballetba Aug 16, 2022
c95b202
Make the comments and naming are all easy to follow.
ksballetba Aug 18, 2022
2ed69d3
Make the comments and naming are all easy to understand.
ksballetba Aug 18, 2022
452b7b5
change nits.
ksballetba Aug 18, 2022
1ec5191
Make the comments are easy to understand.
ksballetba Aug 18, 2022
6df133d
change test case name.
ksballetba Aug 24, 2022
8879a15
Merge branch 'master' into fix_ios_text_selection_not_smooth
ksballetba Aug 26, 2022
54e5ebb
Merge branch 'flutter:master' into fix_ios_text_selection_not_smooth
ksballetba Oct 25, 2022
fc6fc7e
Docs improvements
ksballetba Oct 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions packages/flutter/lib/src/widgets/text_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,12 @@ class TextSelectionOverlay {
if (!renderObject.attached) {
return;
}
final Size handleSize = selectionControls!.getHandleSize(
renderObject.preferredLineHeight,
);

_dragEndPosition = details.globalPosition + Offset(0.0, -handleSize.height);
// This adjusts for the fact that the selection handles may not
// perfectly cover the TextPosition that they correspond to.
final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.endHandleType);
_dragEndPosition = details.globalPosition + offsetFromHandleToTextPosition;

final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition);

_selectionOverlay.showMagnifier(
Expand Down Expand Up @@ -660,10 +661,12 @@ class TextSelectionOverlay {
if (!renderObject.attached) {
return;
}
final Size handleSize = selectionControls!.getHandleSize(
renderObject.preferredLineHeight,
);
_dragStartPosition = details.globalPosition + Offset(0.0, -handleSize.height);

// This adjusts for the fact that the selection handles may not
// perfectly cover the TextPosition that they correspond to.
final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.startHandleType);
_dragStartPosition = details.globalPosition + offsetFromHandleToTextPosition;

final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition);

_selectionOverlay.showMagnifier(
Expand Down Expand Up @@ -731,6 +734,32 @@ class TextSelectionOverlay {

void _handleAnyDragEnd(DragEndDetails details) => _selectionOverlay.hideMagnifier(shouldShowToolbar: !_selection.isCollapsed);

// Returns the offset that locates a drag on a handle to the correct line of text.
Offset _getOffsetToTextPositionPoint(TextSelectionHandleType type) {
final Size handleSize = selectionControls!.getHandleSize(
renderObject.preferredLineHeight,
);

// Try to shift center of handle to top by half of handle height.
final double halfHandleHeight = handleSize.height / 2;

// [getHandleAnchor] is used to shift the selection endpoint to the top left
// point of the handle rect when building the handle widget.
// The endpoint is at the bottom of the selection rect, which is also at the
// bottom of the line of text.
// Try to shift the top of the handle to the selection endpoint by the dy of
// the handle's anchor.
final double handleAnchorDy = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy;

// Try to shift the selection endpoint to the center of the correct line by
// using half of the line height.
final double halfPreferredLineHeight = renderObject.preferredLineHeight / 2;

// The x offset is accurate, so we only need to adjust the y position.
final double offsetYFromHandleToTextPosition = handleAnchorDy - halfHandleHeight - halfPreferredLineHeight;
return Offset(0.0, offsetYFromHandleToTextPosition);
}

void _handleSelectionHandleChanged(TextSelection newSelection, {required bool isEnd}) {
final TextPosition textPosition = isEnd ? newSelection.extent : newSelection.base;
selectionDelegate.userUpdateTextEditingValue(
Expand Down
97 changes: 97 additions & 0 deletions packages/flutter/test/cupertino/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6805,4 +6805,101 @@ void main() {
}, variant: TargetPlatformVariant.all());
});
});

testWidgets('Can drag handles to change selection correctly in multiline', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();

await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: CupertinoTextField(
dragStartBehavior: DragStartBehavior.down,
controller: controller,
style: const TextStyle(color: Colors.black, fontSize: 34.0),
maxLines: 3,
),
),
),
);

const String testValue =
'First line of text is\n'
'Second line goes until\n'
'Third line of stuff';

const String cutValue =
'First line of text is\n'
'Second until\n'
'Third line of stuff';
await tester.enterText(find.byType(CupertinoTextField), testValue);

// Skip past scrolling animation.
await tester.pump();
await tester.pumpAndSettle(const Duration(milliseconds: 200));

// Check that the text spans multiple lines.
final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First'));
final Offset secondPos = textOffsetToPosition(tester, testValue.indexOf('Second'));
final Offset thirdPos = textOffsetToPosition(tester, testValue.indexOf('Third'));
expect(firstPos.dx, secondPos.dx);
expect(firstPos.dx, thirdPos.dx);
expect(firstPos.dy, lessThan(secondPos.dy));
expect(secondPos.dy, lessThan(thirdPos.dy));

// Double tap on the 'n' in 'until' to select the word.
final Offset untilPos = textOffsetToPosition(tester, testValue.indexOf('until')+1);
await tester.tapAt(untilPos);
await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(untilPos);
await tester.pumpAndSettle();

// Skip past the frame where the opacity is zero.
await tester.pump(const Duration(milliseconds: 200));

expect(controller.selection.baseOffset, 39);
expect(controller.selection.extentOffset, 44);

final RenderEditable renderEditable = findRenderEditable(tester);
final List<TextSelectionPoint> endpoints = globalize(
renderEditable.getEndpointsForSelection(controller.selection),
renderEditable,
);
expect(endpoints.length, 2);

final Offset offsetFromEndPointToMiddlePoint = Offset(0.0, -renderEditable.preferredLineHeight / 2);

// Drag the left handle to just after 'Second', still on the second line.
Offset handlePos = endpoints[0].point + offsetFromEndPointToMiddlePoint;
Offset newHandlePos = textOffsetToPosition(tester, testValue.indexOf('Second') + 6) + offsetFromEndPointToMiddlePoint;
TestGesture gesture = await tester.startGesture(handlePos, pointer: 7);
await tester.pump();
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
await tester.pump();

expect(controller.selection.baseOffset, 28);
expect(controller.selection.extentOffset, 44);

// Drag the right handle to just after 'goes', still on the second line.
handlePos = endpoints[1].point + offsetFromEndPointToMiddlePoint;
newHandlePos = textOffsetToPosition(tester, testValue.indexOf('goes') + 4) + offsetFromEndPointToMiddlePoint;
gesture = await tester.startGesture(handlePos, pointer: 7);
await tester.pump();
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
await tester.pump();

expect(controller.selection.baseOffset, 28);
expect(controller.selection.extentOffset, 38);

if (!isContextMenuProvidedByPlatform) {
await tester.tap(find.text('Cut'));
await tester.pump();
expect(controller.selection.isCollapsed, true);
expect(controller.text, cutValue);
}
});
}