From 51a6dce2d4f083f2af973465a30840d97f9d3ab3 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 8 Aug 2022 16:04:01 +0800 Subject: [PATCH 01/21] Fix an issue that Dragging the iOS text selection handles is jumpy (#106703) --- .../src/cupertino/desktop_text_selection.dart | 8 +++++++ .../lib/src/cupertino/text_selection.dart | 8 +++++++ .../src/material/desktop_text_selection.dart | 8 +++++++ .../lib/src/material/text_selection.dart | 10 +++++++++ .../lib/src/widgets/text_selection.dart | 21 +++++++++++++++++-- .../test/cupertino/text_field_test.dart | 5 +++++ .../test/widgets/editable_text_test.dart | 10 +++++++++ .../test/widgets/text_selection_test.dart | 5 +++++ 8 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart index 037c0b059cb47..2ca0e7eb119f5 100644 --- a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart +++ b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart @@ -40,6 +40,14 @@ class _CupertinoDesktopTextSelectionControls extends TextSelectionControls { return Size.zero; } + /// Returns the offset from text selection handle to actual location in text line. + /// + /// Desktop has no text selection handles, return [Offset.zero]. + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } + /// Builder for the Mac-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart index d3be12ca2c66b..a548b69dd7db7 100644 --- a/packages/flutter/lib/src/cupertino/text_selection.dart +++ b/packages/flutter/lib/src/cupertino/text_selection.dart @@ -202,6 +202,14 @@ class CupertinoTextSelectionControls extends TextSelectionControls { ); } + /// Returns the offset from text selection handle to actual location in text line. + /// + /// To Cupertino text selection style,handle overlaps the text line, return [Offset.zero]. + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } + /// Builder for iOS-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/material/desktop_text_selection.dart b/packages/flutter/lib/src/material/desktop_text_selection.dart index 62690434de3d2..02387cb945eb0 100644 --- a/packages/flutter/lib/src/material/desktop_text_selection.dart +++ b/packages/flutter/lib/src/material/desktop_text_selection.dart @@ -26,6 +26,14 @@ class _DesktopTextSelectionControls extends TextSelectionControls { return Size.zero; } + /// Returns the offset from text selection handle to actual location in text line. + /// + /// Desktop has no text selection handles, return [Offset.zero]. + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } + /// Builder for the Material-style desktop copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart index e56cad5c582a6..48be152d67251 100644 --- a/packages/flutter/lib/src/material/text_selection.dart +++ b/packages/flutter/lib/src/material/text_selection.dart @@ -26,6 +26,16 @@ class MaterialTextSelectionControls extends TextSelectionControls { @override Size getHandleSize(double textLineHeight) => const Size(_kHandleSize, _kHandleSize); + /// Returns the offset from text selection handle to actual location in text line. + /// + /// To Material text selection style,handle is located below the text line. + /// The OffsetX from selection handle to text line is 0.0, OffsetY is handle height; + /// Return [Offset(0.0, -handleSize.height)] + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset(0.0, -handleSize.height); + } + /// Builder for material-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 4a125c15d582b..5cd78021766d3 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -275,6 +275,16 @@ abstract class TextSelectionControls { /// Returns the size of the selection handle. Size getHandleSize(double textLineHeight); + /// Returns the offset from text selection handle to actual location in text line. + /// + /// To Material text selection, handle is located below the text line. + /// To Cupertino text selection, handle overlaps the text line. + /// To Desktop text selection, has no text selection handles. + /// + /// For different relative position from selection handle to text line, + /// return different offset for accurate text selection updates. + Offset getOffsetFromHandleToTextPosition(Size handleSize); + /// Whether the current selection of the text field managed by the given /// `delegate` can be removed from the text field and placed into the /// [Clipboard]. @@ -617,7 +627,10 @@ class TextSelectionOverlay { renderObject.preferredLineHeight, ); - _dragEndPosition = details.globalPosition + Offset(0.0, -handleSize.height); + final Offset offsetFromHandleToTextPosition = selectionControls! + .getOffsetFromHandleToTextPosition(handleSize); + + _dragEndPosition = details.globalPosition + offsetFromHandleToTextPosition; final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( @@ -692,7 +705,11 @@ class TextSelectionOverlay { final Size handleSize = selectionControls!.getHandleSize( renderObject.preferredLineHeight, ); - _dragStartPosition = details.globalPosition + Offset(0.0, -handleSize.height); + + final Offset offsetFromHandleToTextPosition = selectionControls! + .getOffsetFromHandleToTextPosition(handleSize); + + _dragStartPosition = details.globalPosition + offsetFromHandleToTextPosition; final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index b5ea6fcb65dd1..84db7bf3b91fa 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -59,6 +59,11 @@ class MockTextSelectionControls extends TextSelectionControls { Size getHandleSize(double textLineHeight) { throw UnimplementedError(); } + + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + throw UnimplementedError(); + } } class PathBoundsMatcher extends Matcher { diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index c635127569717..8ef69377d6d32 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -12755,6 +12755,11 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls { return Offset.zero; } + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } + bool testCanCut = false; bool testCanCopy = false; bool testCanPaste = false; @@ -12853,6 +12858,11 @@ class _CustomTextSelectionControls extends TextSelectionControls { return Size.zero; } + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } + @override Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { return Offset.zero; diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 09980cd962680..129b5257d92fd 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -1471,6 +1471,11 @@ class TextSelectionControlsSpy extends TextSelectionControls { Size getHandleSize(double textLineHeight) { return Size(textLineHeight, textLineHeight); } + + @override + Offset getOffsetFromHandleToTextPosition(Size handleSize) { + return Offset.zero; + } } class FakeClipboardStatusNotifier extends ClipboardStatusNotifier { From e5503cffcf06c7f91bcf39496af69417c6a5136d Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 8 Aug 2022 19:12:43 +0800 Subject: [PATCH 02/21] delete needless backspace --- packages/flutter/lib/src/widgets/text_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 5cd78021766d3..8797c05b26e71 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -280,7 +280,7 @@ abstract class TextSelectionControls { /// To Material text selection, handle is located below the text line. /// To Cupertino text selection, handle overlaps the text line. /// To Desktop text selection, has no text selection handles. - /// + /// /// For different relative position from selection handle to text line, /// return different offset for accurate text selection updates. Offset getOffsetFromHandleToTextPosition(Size handleSize); From dbca95bb09f36d2b4d150f53925ac15e66a138ac Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 11:19:48 +0800 Subject: [PATCH 03/21] Revert "delete needless backspace" This reverts commit e5503cffcf06c7f91bcf39496af69417c6a5136d. --- packages/flutter/lib/src/widgets/text_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 8797c05b26e71..5cd78021766d3 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -280,7 +280,7 @@ abstract class TextSelectionControls { /// To Material text selection, handle is located below the text line. /// To Cupertino text selection, handle overlaps the text line. /// To Desktop text selection, has no text selection handles. - /// + /// /// For different relative position from selection handle to text line, /// return different offset for accurate text selection updates. Offset getOffsetFromHandleToTextPosition(Size handleSize); From cfdea60b8a1e91fbc18b9c3b62d5fe6d11453a98 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 11:19:57 +0800 Subject: [PATCH 04/21] Revert "Fix an issue that Dragging the iOS text selection handles is jumpy (#106703)" This reverts commit 51a6dce2d4f083f2af973465a30840d97f9d3ab3. --- .../src/cupertino/desktop_text_selection.dart | 8 ------- .../lib/src/cupertino/text_selection.dart | 8 ------- .../src/material/desktop_text_selection.dart | 8 ------- .../lib/src/material/text_selection.dart | 10 --------- .../lib/src/widgets/text_selection.dart | 21 ++----------------- .../test/cupertino/text_field_test.dart | 5 ----- .../test/widgets/editable_text_test.dart | 10 --------- .../test/widgets/text_selection_test.dart | 5 ----- 8 files changed, 2 insertions(+), 73 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart index 2ca0e7eb119f5..037c0b059cb47 100644 --- a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart +++ b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart @@ -40,14 +40,6 @@ class _CupertinoDesktopTextSelectionControls extends TextSelectionControls { return Size.zero; } - /// Returns the offset from text selection handle to actual location in text line. - /// - /// Desktop has no text selection handles, return [Offset.zero]. - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } - /// Builder for the Mac-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart index a548b69dd7db7..d3be12ca2c66b 100644 --- a/packages/flutter/lib/src/cupertino/text_selection.dart +++ b/packages/flutter/lib/src/cupertino/text_selection.dart @@ -202,14 +202,6 @@ class CupertinoTextSelectionControls extends TextSelectionControls { ); } - /// Returns the offset from text selection handle to actual location in text line. - /// - /// To Cupertino text selection style,handle overlaps the text line, return [Offset.zero]. - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } - /// Builder for iOS-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/material/desktop_text_selection.dart b/packages/flutter/lib/src/material/desktop_text_selection.dart index 02387cb945eb0..62690434de3d2 100644 --- a/packages/flutter/lib/src/material/desktop_text_selection.dart +++ b/packages/flutter/lib/src/material/desktop_text_selection.dart @@ -26,14 +26,6 @@ class _DesktopTextSelectionControls extends TextSelectionControls { return Size.zero; } - /// Returns the offset from text selection handle to actual location in text line. - /// - /// Desktop has no text selection handles, return [Offset.zero]. - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } - /// Builder for the Material-style desktop copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart index 48be152d67251..e56cad5c582a6 100644 --- a/packages/flutter/lib/src/material/text_selection.dart +++ b/packages/flutter/lib/src/material/text_selection.dart @@ -26,16 +26,6 @@ class MaterialTextSelectionControls extends TextSelectionControls { @override Size getHandleSize(double textLineHeight) => const Size(_kHandleSize, _kHandleSize); - /// Returns the offset from text selection handle to actual location in text line. - /// - /// To Material text selection style,handle is located below the text line. - /// The OffsetX from selection handle to text line is 0.0, OffsetY is handle height; - /// Return [Offset(0.0, -handleSize.height)] - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset(0.0, -handleSize.height); - } - /// Builder for material-style copy/paste text selection toolbar. @override Widget buildToolbar( diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 5cd78021766d3..4a125c15d582b 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -275,16 +275,6 @@ abstract class TextSelectionControls { /// Returns the size of the selection handle. Size getHandleSize(double textLineHeight); - /// Returns the offset from text selection handle to actual location in text line. - /// - /// To Material text selection, handle is located below the text line. - /// To Cupertino text selection, handle overlaps the text line. - /// To Desktop text selection, has no text selection handles. - /// - /// For different relative position from selection handle to text line, - /// return different offset for accurate text selection updates. - Offset getOffsetFromHandleToTextPosition(Size handleSize); - /// Whether the current selection of the text field managed by the given /// `delegate` can be removed from the text field and placed into the /// [Clipboard]. @@ -627,10 +617,7 @@ class TextSelectionOverlay { renderObject.preferredLineHeight, ); - final Offset offsetFromHandleToTextPosition = selectionControls! - .getOffsetFromHandleToTextPosition(handleSize); - - _dragEndPosition = details.globalPosition + offsetFromHandleToTextPosition; + _dragEndPosition = details.globalPosition + Offset(0.0, -handleSize.height); final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( @@ -705,11 +692,7 @@ class TextSelectionOverlay { final Size handleSize = selectionControls!.getHandleSize( renderObject.preferredLineHeight, ); - - final Offset offsetFromHandleToTextPosition = selectionControls! - .getOffsetFromHandleToTextPosition(handleSize); - - _dragStartPosition = details.globalPosition + offsetFromHandleToTextPosition; + _dragStartPosition = details.globalPosition + Offset(0.0, -handleSize.height); final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 84db7bf3b91fa..b5ea6fcb65dd1 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -59,11 +59,6 @@ class MockTextSelectionControls extends TextSelectionControls { Size getHandleSize(double textLineHeight) { throw UnimplementedError(); } - - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - throw UnimplementedError(); - } } class PathBoundsMatcher extends Matcher { diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 8ef69377d6d32..c635127569717 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -12755,11 +12755,6 @@ class MockTextSelectionControls extends Fake implements TextSelectionControls { return Offset.zero; } - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } - bool testCanCut = false; bool testCanCopy = false; bool testCanPaste = false; @@ -12858,11 +12853,6 @@ class _CustomTextSelectionControls extends TextSelectionControls { return Size.zero; } - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } - @override Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { return Offset.zero; diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 129b5257d92fd..09980cd962680 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -1471,11 +1471,6 @@ class TextSelectionControlsSpy extends TextSelectionControls { Size getHandleSize(double textLineHeight) { return Size(textLineHeight, textLineHeight); } - - @override - Offset getOffsetFromHandleToTextPosition(Size handleSize) { - return Offset.zero; - } } class FakeClipboardStatusNotifier extends ClipboardStatusNotifier { From 56f72eb06ab1c2ec1b0ad6e1d25adf207a5ff381 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 14:58:25 +0800 Subject: [PATCH 05/21] Fix an issue that Dragging the iOS text selection update wrongly without separate value (#106703) --- .../lib/src/widgets/text_selection.dart | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 4a125c15d582b..e4a978366c671 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -613,11 +613,13 @@ class TextSelectionOverlay { if (!renderObject.attached) { return; } - final Size handleSize = selectionControls!.getHandleSize( - renderObject.preferredLineHeight, - ); - _dragEndPosition = details.globalPosition + Offset(0.0, -handleSize.height); + ///[details.globalPosition] is the drag point,need shift to + ///text middle point that RenderEditable use to get [TextPosition] + ///and update selection for end handle. + final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.endHandleType); + _dragEndPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; + final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( @@ -689,10 +691,13 @@ class TextSelectionOverlay { if (!renderObject.attached) { return; } - final Size handleSize = selectionControls!.getHandleSize( - renderObject.preferredLineHeight, - ); - _dragStartPosition = details.globalPosition + Offset(0.0, -handleSize.height); + + ///[details.globalPosition] is the drag point,need shift to + ///text middle point that RenderEditable use to get [TextPosition] + ///and update selection for start handle. + final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.startHandleType); + _dragStartPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; + final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition); _selectionOverlay.showMagnifier(MagnifierOverlayInfoBearer._fromRenderEditable( @@ -758,6 +763,28 @@ class TextSelectionOverlay { void _handleAnyDragEnd(DragEndDetails details) => _selectionOverlay.hideMagnifier(shouldShowToolbar: !_selection.isCollapsed); + Offset _getOffsetToTextMiddlePoint(TextSelectionHandleType type) { + final Size handleSize = selectionControls!.getHandleSize( + renderObject.preferredLineHeight, + ); + + /// The drag events always happens near the center of handle. + /// so ues half of the handle size to shift drag point to handle anchor. + /// handle anchor is left top point of handle rect. + final Offset offsetToHandleAnchor = Offset(-handleSize.width / 2, -handleSize.height / 2); + + /// [getHandleAnchor] use to shift selection end point to handle anchor. + /// now need shift handle anchor to selection end point. + /// end point is [ui.TextBox] start bottom point of first or last selected word. + final Offset offsetToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight); + + /// For more accurate selection updates,need shift selection end point + /// to selection middle point. + final Offset offsetToTextMiddlePoint = Offset(0.0, -renderObject.preferredLineHeight / 2); + + return offsetToHandleAnchor + offsetToTextEndPoint + offsetToTextMiddlePoint; + } + void _handleSelectionHandleChanged(TextSelection newSelection, {required bool isEnd}) { final TextPosition textPosition = isEnd ? newSelection.extent : newSelection.base; selectionDelegate.userUpdateTextEditingValue( From 89bf0036e553d24182d15f445a3be8bfefa8b670 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 15:44:10 +0800 Subject: [PATCH 06/21] make selection update more accurately --- .../flutter/lib/src/widgets/text_selection.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index e4a978366c671..5f06fe254a38e 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -778,9 +778,24 @@ class TextSelectionOverlay { /// end point is [ui.TextBox] start bottom point of first or last selected word. final Offset offsetToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight); + + double getOffsetXToTextMiddlePoint( + TextSelectionHandleType type, + ) { + switch (type) { + case TextSelectionHandleType.left: + return -handleSize.width / 2; + case TextSelectionHandleType.right: + return handleSize.width / 2; + case TextSelectionHandleType.collapsed: + return 0.0; + } + } + /// For more accurate selection updates,need shift selection end point /// to selection middle point. - final Offset offsetToTextMiddlePoint = Offset(0.0, -renderObject.preferredLineHeight / 2); + final double offsetXToTextMiddlePoint = getOffsetXToTextMiddlePoint(type); + final Offset offsetToTextMiddlePoint = Offset(offsetXToTextMiddlePoint, -renderObject.preferredLineHeight / 2); return offsetToHandleAnchor + offsetToTextEndPoint + offsetToTextMiddlePoint; } From fb728827d8794e0deee2fed2e628514310f4a4a3 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 16:43:58 +0800 Subject: [PATCH 07/21] make selection update more accurately --- .../lib/src/widgets/text_selection.dart | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 5f06fe254a38e..6eca1e0ffc623 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -771,33 +771,21 @@ class TextSelectionOverlay { /// The drag events always happens near the center of handle. /// so ues half of the handle size to shift drag point to handle anchor. /// handle anchor is left top point of handle rect. - final Offset offsetToHandleAnchor = Offset(-handleSize.width / 2, -handleSize.height / 2); + final double offsetYToHandleAnchor = -handleSize.height / 2; /// [getHandleAnchor] use to shift selection end point to handle anchor. /// now need shift handle anchor to selection end point. /// end point is [ui.TextBox] start bottom point of first or last selected word. - final Offset offsetToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight); - - - double getOffsetXToTextMiddlePoint( - TextSelectionHandleType type, - ) { - switch (type) { - case TextSelectionHandleType.left: - return -handleSize.width / 2; - case TextSelectionHandleType.right: - return handleSize.width / 2; - case TextSelectionHandleType.collapsed: - return 0.0; - } - } + final double offsetYToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy; /// For more accurate selection updates,need shift selection end point /// to selection middle point. - final double offsetXToTextMiddlePoint = getOffsetXToTextMiddlePoint(type); - final Offset offsetToTextMiddlePoint = Offset(offsetXToTextMiddlePoint, -renderObject.preferredLineHeight / 2); + final double offsetYToTextMiddlePoint = -renderObject.preferredLineHeight / 2; - return offsetToHandleAnchor + offsetToTextEndPoint + offsetToTextMiddlePoint; + /// When dragging handle and move,the pointX from drag event is accurate. + /// Only need to compute different offsetY from different handle. + final double offsetYFromDragPointToTextMiddlePoint = offsetYToHandleAnchor + offsetYToTextEndPoint + offsetYToTextMiddlePoint; + return Offset(0.0, offsetYFromDragPointToTextMiddlePoint); } void _handleSelectionHandleChanged(TextSelection newSelection, {required bool isEnd}) { From d255009b2871a6ccd1432008ca58a8fc27ac03d4 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 19:26:25 +0800 Subject: [PATCH 08/21] add new test to make sure cupertino text field can drag handles to change selection correctly in multiline. --- .../test/cupertino/text_field_test.dart | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index b5ea6fcb65dd1..643a66497c069 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6245,4 +6245,102 @@ void main() { }, variant: TargetPlatformVariant.all()); }); }); + + testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async { + const String kThreeLines = + 'First line of text is\n' + 'Second line goes until\n' + 'Third line of stuff'; + + 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 = kThreeLines; + 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.pump(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')); + final Offset middleStringPos = textOffsetToPosition(tester, testValue.indexOf('irst')); + 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(); + + await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero + + expect(controller.selection.baseOffset, 39); + expect(controller.selection.extentOffset, 44); + + final RenderEditable renderEditable = findRenderEditable(tester); + final List 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 the second line, just after 'Second'. + 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 the first line, just after 'First'. + 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); + } + }); } From 58f1569a44c6dcba0a4672d17f7585f815ecb497 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 19:36:50 +0800 Subject: [PATCH 09/21] clean unused local variable --- packages/flutter/test/cupertino/text_field_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 643a66497c069..38cc0f80fb24e 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6283,7 +6283,6 @@ void main() { final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First')); final Offset secondPos = textOffsetToPosition(tester, testValue.indexOf('Second')); final Offset thirdPos = textOffsetToPosition(tester, testValue.indexOf('Third')); - final Offset middleStringPos = textOffsetToPosition(tester, testValue.indexOf('irst')); expect(firstPos.dx, secondPos.dx); expect(firstPos.dx, thirdPos.dx); expect(firstPos.dy, lessThan(secondPos.dy)); From cb07af8b40cb8f66355661606a33f89e3dc18c95 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 19:39:36 +0800 Subject: [PATCH 10/21] correct the test documentation. --- packages/flutter/test/cupertino/text_field_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 38cc0f80fb24e..93a3ff689f3ef 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6322,7 +6322,7 @@ void main() { expect(controller.selection.baseOffset, 28); expect(controller.selection.extentOffset, 44); - // Drag the right handle to the first line, just after 'First'. + // Drag the right handle to the second line, just after 'goes'. handlePos = endpoints[1].point + offsetFromEndPointToMiddlePoint; newHandlePos = textOffsetToPosition(tester, testValue.indexOf('goes') + 4) + offsetFromEndPointToMiddlePoint; gesture = await tester.startGesture(handlePos, pointer: 7); From 685ccaf7b19a0a553bd21a72b845850205bdea58 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Mon, 15 Aug 2022 19:41:19 +0800 Subject: [PATCH 11/21] clean unused local variable. --- packages/flutter/test/cupertino/text_field_test.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 93a3ff689f3ef..17db09d87443b 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6247,11 +6247,6 @@ void main() { }); testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async { - const String kThreeLines = - 'First line of text is\n' - 'Second line goes until\n' - 'Third line of stuff'; - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( @@ -6268,7 +6263,11 @@ void main() { ), ); - const String testValue = kThreeLines; + 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' From cb0d598379f58e49cc0ffb6805c997af592c034c Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Tue, 16 Aug 2022 10:50:53 +0800 Subject: [PATCH 12/21] change nits. --- .../lib/src/widgets/text_selection.dart | 33 ++++++++++--------- .../test/cupertino/text_field_test.dart | 9 +++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 6eca1e0ffc623..1766296b065f3 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -614,9 +614,9 @@ class TextSelectionOverlay { return; } - ///[details.globalPosition] is the drag point,need shift to - ///text middle point that RenderEditable use to get [TextPosition] - ///and update selection for end handle. + // [details.globalPosition] is the drag point, it needs to be shifted + // to the text middle point that [RenderEditable] uses to get [TextPosition] + // and update selection for end handle. final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.endHandleType); _dragEndPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; @@ -692,9 +692,9 @@ class TextSelectionOverlay { return; } - ///[details.globalPosition] is the drag point,need shift to - ///text middle point that RenderEditable use to get [TextPosition] - ///and update selection for start handle. + // [details.globalPosition] is the drag point, it needs to be shifted + // to the text middle point that [RenderEditable] uses to get [TextPosition] + // and update selection for end handle. final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.startHandleType); _dragStartPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; @@ -768,22 +768,23 @@ class TextSelectionOverlay { renderObject.preferredLineHeight, ); - /// The drag events always happens near the center of handle. - /// so ues half of the handle size to shift drag point to handle anchor. - /// handle anchor is left top point of handle rect. + // The center of the handle is where it's easier to drag, and + // [details.globalPosition] is probably close to center. + // So use half of the handle size to shift drag point to handle anchor. + // Handle anchor is left top point of handle rect. final double offsetYToHandleAnchor = -handleSize.height / 2; - /// [getHandleAnchor] use to shift selection end point to handle anchor. - /// now need shift handle anchor to selection end point. - /// end point is [ui.TextBox] start bottom point of first or last selected word. + // [getHandleAnchor] use to shift selection end point to handle anchor. + // Now need shift handle anchor to selection end point. + // End point is [ui.TextBox] start bottom point of first or last selected word. final double offsetYToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy; - /// For more accurate selection updates,need shift selection end point - /// to selection middle point. + // For more accurate selection updates,need shift selection end point + // to selection middle point. final double offsetYToTextMiddlePoint = -renderObject.preferredLineHeight / 2; - /// When dragging handle and move,the pointX from drag event is accurate. - /// Only need to compute different offsetY from different handle. + // When dragging handle and move,the pointX from drag event is accurate. + // Only need to compute different offsetY from different handle. final double offsetYFromDragPointToTextMiddlePoint = offsetYToHandleAnchor + offsetYToTextEndPoint + offsetYToTextMiddlePoint; return Offset(0.0, offsetYFromDragPointToTextMiddlePoint); } diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 17db09d87443b..4af817ed6ef09 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6274,9 +6274,8 @@ void main() { 'Third line of stuff'; await tester.enterText(find.byType(CupertinoTextField), testValue); - // skip past scrolling animation - await tester.pump(); - await tester.pump(const Duration(milliseconds: 200)); + // Skip past scrolling animation. + await tester.pumpAndSettle(const Duration(milliseconds: 200)); // Check that the text spans multiple lines. final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First')); @@ -6308,7 +6307,7 @@ void main() { final Offset offsetFromEndPointToMiddlePoint = Offset(0.0, -renderEditable.preferredLineHeight / 2); - // Drag the left handle to the second line, just after 'Second'. + // 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); @@ -6321,7 +6320,7 @@ void main() { expect(controller.selection.baseOffset, 28); expect(controller.selection.extentOffset, 44); - // Drag the right handle to the second line, just after 'goes'. + // 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); From 60ba360585c117c41ac1a4b7689792f6aa81f739 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Tue, 16 Aug 2022 11:45:36 +0800 Subject: [PATCH 13/21] change nits. --- packages/flutter/lib/src/widgets/text_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 1766296b065f3..de83709c8434b 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -694,7 +694,7 @@ class TextSelectionOverlay { // [details.globalPosition] is the drag point, it needs to be shifted // to the text middle point that [RenderEditable] uses to get [TextPosition] - // and update selection for end handle. + // and update selection for start handle. final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.startHandleType); _dragStartPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; From 73918635a53cf0735406bb5677e1dda6b8b228fb Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Tue, 16 Aug 2022 14:21:21 +0800 Subject: [PATCH 14/21] change nits. --- packages/flutter/test/cupertino/text_field_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 4af817ed6ef09..1067041dc9cca 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6275,7 +6275,8 @@ void main() { await tester.enterText(find.byType(CupertinoTextField), testValue); // Skip past scrolling animation. - await tester.pumpAndSettle(const Duration(milliseconds: 200)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); // Check that the text spans multiple lines. final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First')); @@ -6293,7 +6294,8 @@ void main() { await tester.tapAt(untilPos); await tester.pumpAndSettle(); - await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero + // 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); From 6f3805bc28c2a1a0ed961e130d372f7215f26940 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Tue, 16 Aug 2022 14:30:16 +0800 Subject: [PATCH 15/21] change nits. --- packages/flutter/test/cupertino/text_field_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 1067041dc9cca..e329c24a1e994 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6276,7 +6276,7 @@ void main() { // Skip past scrolling animation. await tester.pump(); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); // Check that the text spans multiple lines. final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First')); From c95b2028a5041181de6d6250700294ede6929199 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Thu, 18 Aug 2022 11:23:57 +0800 Subject: [PATCH 16/21] Make the comments and naming are all easy to follow. --- .../lib/src/widgets/text_selection.dart | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index de83709c8434b..3ec6f2ed339ce 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -615,10 +615,12 @@ class TextSelectionOverlay { } // [details.globalPosition] is the drag point, it needs to be shifted - // to the text middle point that [RenderEditable] uses to get [TextPosition] + // to the text position point that [RenderEditable] uses to get [TextPosition] // and update selection for end handle. - final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.endHandleType); - _dragEndPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; + // Dx of text position point need to be consistent with dx of drag point. + // Dy of text position point need to be locate in the correct text line. + final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.endHandleType); + _dragEndPosition = details.globalPosition + offsetFromHandleToTextPosition; final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition); @@ -693,10 +695,12 @@ class TextSelectionOverlay { } // [details.globalPosition] is the drag point, it needs to be shifted - // to the text middle point that [RenderEditable] uses to get [TextPosition] + // to the text position point that [RenderEditable] uses to get [TextPosition] // and update selection for start handle. - final Offset offsetFromDragPointToTextMiddlePoint = _getOffsetToTextMiddlePoint(_selectionOverlay.startHandleType); - _dragStartPosition = details.globalPosition + offsetFromDragPointToTextMiddlePoint; + // Dx of text position point need to be consistent with dx of drag point. + // Dy of text position point need to be locate in the correct text line. + final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.startHandleType); + _dragStartPosition = details.globalPosition + offsetFromHandleToTextPosition; final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition); @@ -763,30 +767,38 @@ class TextSelectionOverlay { void _handleAnyDragEnd(DragEndDetails details) => _selectionOverlay.hideMagnifier(shouldShowToolbar: !_selection.isCollapsed); - Offset _getOffsetToTextMiddlePoint(TextSelectionHandleType type) { + // The center of the handle is where it's easier to drag, and + // [details.globalPosition] is probably close to center. + // Suppose center of handle as start point, center of correct + // text line as target point. Return offset from start + // point to target point to shift real drag point to text position + // point. Make sure even if the drag events happened on other position + // of handle, not center. The text position point always locate in + // correct text line. + Offset _getOffsetToTextPositionPoint(TextSelectionHandleType type) { final Size handleSize = selectionControls!.getHandleSize( renderObject.preferredLineHeight, ); - // The center of the handle is where it's easier to drag, and - // [details.globalPosition] is probably close to center. - // So use half of the handle size to shift drag point to handle anchor. - // Handle anchor is left top point of handle rect. - final double offsetYToHandleAnchor = -handleSize.height / 2; + // Dy of offset from center of handle to top is half of handle height. + // Try to shift drag point to point near handle top. + final double offsetYFromHandleCenterToTop = -handleSize.height / 2; - // [getHandleAnchor] use to shift selection end point to handle anchor. - // Now need shift handle anchor to selection end point. - // End point is [ui.TextBox] start bottom point of first or last selected word. - final double offsetYToTextEndPoint = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy; + // [getHandleAnchor] use to shift selection end point to left top point + // of handle rect when build handle widget. + // Selection end point is [ui.TextBox] start bottom point of first or + // last selected word. + // Now use the dy of [getHandleAnchor] try to shift handle top to selection + // end point. + final double handleAnchorDy = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy; - // For more accurate selection updates,need shift selection end point - // to selection middle point. - final double offsetYToTextMiddlePoint = -renderObject.preferredLineHeight / 2; + // Try to shift selection end point to center of correct text line. + final double offsetYToTextLineMiddlePoint = -renderObject.preferredLineHeight / 2; - // When dragging handle and move,the pointX from drag event is accurate. + // When dragging handle and move,the offsetX from drag event is accurate. // Only need to compute different offsetY from different handle. - final double offsetYFromDragPointToTextMiddlePoint = offsetYToHandleAnchor + offsetYToTextEndPoint + offsetYToTextMiddlePoint; - return Offset(0.0, offsetYFromDragPointToTextMiddlePoint); + final double offsetYFromHandleToTextPosition = offsetYFromHandleCenterToTop + handleAnchorDy + offsetYToTextLineMiddlePoint; + return Offset(0.0, offsetYFromHandleToTextPosition); } void _handleSelectionHandleChanged(TextSelection newSelection, {required bool isEnd}) { From 2ed69d381db5d2e53f56a867ef166a46e38df5d9 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Thu, 18 Aug 2022 14:32:04 +0800 Subject: [PATCH 17/21] Make the comments and naming are all easy to understand. --- .../lib/src/widgets/text_selection.dart | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 3ec6f2ed339ce..e5f9851fb8adb 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -615,8 +615,8 @@ class TextSelectionOverlay { } // [details.globalPosition] is the drag point, it needs to be shifted - // to the text position point that [RenderEditable] uses to get [TextPosition] - // and update selection for end handle. + // to the text position point that [RenderEditable] uses to get + // [TextPosition] and update selection for end handle correctly. // Dx of text position point need to be consistent with dx of drag point. // Dy of text position point need to be locate in the correct text line. final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.endHandleType); @@ -695,8 +695,8 @@ class TextSelectionOverlay { } // [details.globalPosition] is the drag point, it needs to be shifted - // to the text position point that [RenderEditable] uses to get [TextPosition] - // and update selection for start handle. + // to the text position point that [RenderEditable] uses to get + // [TextPosition] and update selection for start handle correctly. // Dx of text position point need to be consistent with dx of drag point. // Dy of text position point need to be locate in the correct text line. final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint(_selectionOverlay.startHandleType); @@ -769,10 +769,9 @@ class TextSelectionOverlay { // The center of the handle is where it's easier to drag, and // [details.globalPosition] is probably close to center. - // Suppose center of handle as start point, center of correct - // text line as target point. Return offset from start - // point to target point to shift real drag point to text position - // point. Make sure even if the drag events happened on other position + // Return offset from center of handle to center of correct + // text line to shift real drag point on handle to text position point. + // Make sure even if the drag events happened on other position // of handle, not center. The text position point always locate in // correct text line. Offset _getOffsetToTextPositionPoint(TextSelectionHandleType type) { @@ -780,24 +779,23 @@ class TextSelectionOverlay { renderObject.preferredLineHeight, ); - // Dy of offset from center of handle to top is half of handle height. - // Try to shift drag point to point near handle top. - final double offsetYFromHandleCenterToTop = -handleSize.height / 2; + // Try to shift center of handle to top by half of handle height. + final double halfHandleHeight = handleSize.height / 2; // [getHandleAnchor] use to shift selection end point to left top point // of handle rect when build handle widget. - // Selection end point is [ui.TextBox] start bottom point of first or - // last selected word. - // Now use the dy of [getHandleAnchor] try to shift handle top to selection - // end point. + // End point is at bottom of selection rect, which is at the bottom of + // the text line too. + // Try to shift handle top to selection end point by dy of handle anchor. final double handleAnchorDy = selectionControls!.getHandleAnchor(type, renderObject.preferredLineHeight).dy; - // Try to shift selection end point to center of correct text line. - final double offsetYToTextLineMiddlePoint = -renderObject.preferredLineHeight / 2; + // Try to shift selection end point to center of correct text line + // by half preferred line height. + final double halfPreferredLineHeight = renderObject.preferredLineHeight / 2; // When dragging handle and move,the offsetX from drag event is accurate. // Only need to compute different offsetY from different handle. - final double offsetYFromHandleToTextPosition = offsetYFromHandleCenterToTop + handleAnchorDy + offsetYToTextLineMiddlePoint; + final double offsetYFromHandleToTextPosition = handleAnchorDy - halfHandleHeight - halfPreferredLineHeight; return Offset(0.0, offsetYFromHandleToTextPosition); } From 452b7b51fc6fd39a6ca7c5777ee7ceedece96aa1 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Thu, 18 Aug 2022 14:44:19 +0800 Subject: [PATCH 18/21] change nits. --- packages/flutter/lib/src/widgets/text_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index e5f9851fb8adb..6d441b4883881 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -793,7 +793,7 @@ class TextSelectionOverlay { // by half preferred line height. final double halfPreferredLineHeight = renderObject.preferredLineHeight / 2; - // When dragging handle and move,the offsetX from drag event is accurate. + // When dragging handle and move, the offsetX from drag event is accurate. // Only need to compute different offsetY from different handle. final double offsetYFromHandleToTextPosition = handleAnchorDy - halfHandleHeight - halfPreferredLineHeight; return Offset(0.0, offsetYFromHandleToTextPosition); From 1ec5191b43fd2e537e4123e45b802f8e383dae52 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Thu, 18 Aug 2022 19:11:41 +0800 Subject: [PATCH 19/21] Make the comments are easy to understand. --- packages/flutter/lib/src/widgets/text_selection.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 6d441b4883881..94ad83ee4d561 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -769,8 +769,8 @@ class TextSelectionOverlay { // The center of the handle is where it's easier to drag, and // [details.globalPosition] is probably close to center. - // Return offset from center of handle to center of correct - // text line to shift real drag point on handle to text position point. + // Return offset from center of handle to center of correct text line. + // The offset use to shift real drag point on handle to text position point. // Make sure even if the drag events happened on other position // of handle, not center. The text position point always locate in // correct text line. From 6df133d42ebf266255ef22c6164a6418252980e6 Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Wed, 24 Aug 2022 10:32:16 +0800 Subject: [PATCH 20/21] change test case name. --- packages/flutter/test/cupertino/text_field_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index e329c24a1e994..35abc0605dfe3 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6246,7 +6246,7 @@ void main() { }); }); - testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async { + testWidgets('Can drag handles to change selection correctly in multiline', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( From fc6fc7edd8e8ae1e4a22f0b4f42e5de23a3da97c Mon Sep 17 00:00:00 2001 From: ksballetba <2501226111@qq.com> Date: Tue, 25 Oct 2022 10:35:44 +0800 Subject: [PATCH 21/21] Docs improvements --- .../lib/src/widgets/text_selection.dart | 40 +++++++------------ .../test/cupertino/text_field_test.dart | 4 +- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 0b3086b1ec173..825b8cf9a4b73 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -583,11 +583,8 @@ class TextSelectionOverlay { return; } - // [details.globalPosition] is the drag point, it needs to be shifted - // to the text position point that [RenderEditable] uses to get - // [TextPosition] and update selection for end handle correctly. - // Dx of text position point need to be consistent with dx of drag point. - // Dy of text position point need to be locate in the correct text line. + // 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; @@ -665,11 +662,8 @@ class TextSelectionOverlay { return; } - // [details.globalPosition] is the drag point, it needs to be shifted - // to the text position point that [RenderEditable] uses to get - // [TextPosition] and update selection for start handle correctly. - // Dx of text position point need to be consistent with dx of drag point. - // Dy of text position point need to be locate in the correct text line. + // 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; @@ -740,13 +734,7 @@ class TextSelectionOverlay { void _handleAnyDragEnd(DragEndDetails details) => _selectionOverlay.hideMagnifier(shouldShowToolbar: !_selection.isCollapsed); - // The center of the handle is where it's easier to drag, and - // [details.globalPosition] is probably close to center. - // Return offset from center of handle to center of correct text line. - // The offset use to shift real drag point on handle to text position point. - // Make sure even if the drag events happened on other position - // of handle, not center. The text position point always locate in - // correct text line. + // 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, @@ -755,19 +743,19 @@ class TextSelectionOverlay { // Try to shift center of handle to top by half of handle height. final double halfHandleHeight = handleSize.height / 2; - // [getHandleAnchor] use to shift selection end point to left top point - // of handle rect when build handle widget. - // End point is at bottom of selection rect, which is at the bottom of - // the text line too. - // Try to shift handle top to selection end point by dy of handle anchor. + // [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 selection end point to center of correct text line - // by half preferred line height. + // 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; - // When dragging handle and move, the offsetX from drag event is accurate. - // Only need to compute different offsetY from different handle. + // 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); } diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 18c6e89251cc6..f646deec9186d 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6854,7 +6854,7 @@ void main() { await tester.tapAt(untilPos); await tester.pumpAndSettle(); - // skip past the frame where the opacity is zero + // Skip past the frame where the opacity is zero. await tester.pump(const Duration(milliseconds: 200)); expect(controller.selection.baseOffset, 39); @@ -6869,7 +6869,7 @@ void main() { final Offset offsetFromEndPointToMiddlePoint = Offset(0.0, -renderEditable.preferredLineHeight / 2); - // Drag the left handle to just after 'Second', still on the second line.. + // 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);