@@ -637,7 +637,13 @@ class TextSelectionOverlay {
637637 );
638638 }
639639
640- late Offset _dragEndPosition;
640+ // The contact position of the gesture at the current end handle location.
641+ // Updated when the handle moves.
642+ late double _endHandleDragPosition;
643+
644+ // The distance from _endHandleDragPosition to the center of the line that it
645+ // corresponds to.
646+ late double _endHandleDragPositionToCenterOfLine;
641647
642648 void _handleSelectionEndHandleDragStart (DragStartDetails details) {
643649 if (! renderObject.attached) {
@@ -646,10 +652,17 @@ class TextSelectionOverlay {
646652
647653 // This adjusts for the fact that the selection handles may not
648654 // perfectly cover the TextPosition that they correspond to.
649- final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint (_selectionOverlay.endHandleType);
650- _dragEndPosition = details.globalPosition + offsetFromHandleToTextPosition;
651-
652- final TextPosition position = renderObject.getPositionForPoint (_dragEndPosition);
655+ _endHandleDragPosition = details.globalPosition.dy;
656+ final Offset endPoint =
657+ renderObject.localToGlobal (_selectionOverlay.selectionEndpoints.last.point);
658+ final double centerOfLine = endPoint.dy - renderObject.preferredLineHeight / 2 ;
659+ _endHandleDragPositionToCenterOfLine = centerOfLine - _endHandleDragPosition;
660+ final TextPosition position = renderObject.getPositionForPoint (
661+ Offset (
662+ details.globalPosition.dx,
663+ centerOfLine,
664+ ),
665+ );
653666
654667 _selectionOverlay.showMagnifier (
655668 _buildMagnifier (
@@ -660,14 +673,33 @@ class TextSelectionOverlay {
660673 );
661674 }
662675
676+ /// Given a handle position and drag position, returns the position of handle
677+ /// after the drag.
678+ ///
679+ /// The handle jumps instantly between lines when the drag reaches a full
680+ /// line's height away from the original handle position. In other words, the
681+ /// line jump happens when the contact point would be located at the same
682+ /// place on the handle at the new line as when the gesture started.
683+ double _getHandleDy (double dragDy, double handleDy) {
684+ final double distanceDragged = dragDy - handleDy;
685+ final int dragDirection = distanceDragged < 0.0 ? - 1 : 1 ;
686+ final int linesDragged =
687+ dragDirection * (distanceDragged.abs () / renderObject.preferredLineHeight).floor ();
688+ return handleDy + linesDragged * renderObject.preferredLineHeight;
689+ }
690+
663691 void _handleSelectionEndHandleDragUpdate (DragUpdateDetails details) {
664692 if (! renderObject.attached) {
665693 return ;
666694 }
667- _dragEndPosition += details.delta;
668695
669- final TextPosition position = renderObject.getPositionForPoint (_dragEndPosition);
670- final TextSelection currentSelection = TextSelection .fromPosition (position);
696+ _endHandleDragPosition = _getHandleDy (details.globalPosition.dy, _endHandleDragPosition);
697+ final Offset adjustedOffset = Offset (
698+ details.globalPosition.dx,
699+ _endHandleDragPosition + _endHandleDragPositionToCenterOfLine,
700+ );
701+
702+ final TextPosition position = renderObject.getPositionForPoint (adjustedOffset);
671703
672704 if (_selection.isCollapsed) {
673705 _selectionOverlay.updateMagnifier (_buildMagnifier (
@@ -676,6 +708,7 @@ class TextSelectionOverlay {
676708 renderEditable: renderObject,
677709 ));
678710
711+ final TextSelection currentSelection = TextSelection .fromPosition (position);
679712 _handleSelectionHandleChanged (currentSelection, isEnd: true );
680713 return ;
681714 }
@@ -716,7 +749,13 @@ class TextSelectionOverlay {
716749 ));
717750 }
718751
719- late Offset _dragStartPosition;
752+ // The contact position of the gesture at the current start handle location.
753+ // Updated when the handle moves.
754+ late double _startHandleDragPosition;
755+
756+ // The distance from _startHandleDragPosition to the center of the line that
757+ // it corresponds to.
758+ late double _startHandleDragPositionToCenterOfLine;
720759
721760 void _handleSelectionStartHandleDragStart (DragStartDetails details) {
722761 if (! renderObject.attached) {
@@ -725,10 +764,17 @@ class TextSelectionOverlay {
725764
726765 // This adjusts for the fact that the selection handles may not
727766 // perfectly cover the TextPosition that they correspond to.
728- final Offset offsetFromHandleToTextPosition = _getOffsetToTextPositionPoint (_selectionOverlay.startHandleType);
729- _dragStartPosition = details.globalPosition + offsetFromHandleToTextPosition;
730-
731- final TextPosition position = renderObject.getPositionForPoint (_dragStartPosition);
767+ _startHandleDragPosition = details.globalPosition.dy;
768+ final Offset startPoint =
769+ renderObject.localToGlobal (_selectionOverlay.selectionEndpoints.first.point);
770+ final double centerOfLine = startPoint.dy - renderObject.preferredLineHeight / 2 ;
771+ _startHandleDragPositionToCenterOfLine = centerOfLine - _startHandleDragPosition;
772+ final TextPosition position = renderObject.getPositionForPoint (
773+ Offset (
774+ details.globalPosition.dx,
775+ centerOfLine,
776+ ),
777+ );
732778
733779 _selectionOverlay.showMagnifier (
734780 _buildMagnifier (
@@ -743,8 +789,13 @@ class TextSelectionOverlay {
743789 if (! renderObject.attached) {
744790 return ;
745791 }
746- _dragStartPosition += details.delta;
747- final TextPosition position = renderObject.getPositionForPoint (_dragStartPosition);
792+
793+ _startHandleDragPosition = _getHandleDy (details.globalPosition.dy, _startHandleDragPosition);
794+ final Offset adjustedOffset = Offset (
795+ details.globalPosition.dx,
796+ _startHandleDragPosition + _startHandleDragPositionToCenterOfLine,
797+ );
798+ final TextPosition position = renderObject.getPositionForPoint (adjustedOffset);
748799
749800 if (_selection.isCollapsed) {
750801 _selectionOverlay.updateMagnifier (_buildMagnifier (
@@ -753,7 +804,8 @@ class TextSelectionOverlay {
753804 renderEditable: renderObject,
754805 ));
755806
756- _handleSelectionHandleChanged (TextSelection .fromPosition (position), isEnd: false );
807+ final TextSelection currentSelection = TextSelection .fromPosition (position);
808+ _handleSelectionHandleChanged (currentSelection, isEnd: false );
757809 return ;
758810 }
759811
@@ -813,32 +865,6 @@ class TextSelectionOverlay {
813865 }
814866 }
815867
816- // Returns the offset that locates a drag on a handle to the correct line of text.
817- Offset _getOffsetToTextPositionPoint (TextSelectionHandleType type) {
818- final Size handleSize = selectionControls! .getHandleSize (
819- renderObject.preferredLineHeight,
820- );
821-
822- // Try to shift center of handle to top by half of handle height.
823- final double halfHandleHeight = handleSize.height / 2 ;
824-
825- // [getHandleAnchor] is used to shift the selection endpoint to the top left
826- // point of the handle rect when building the handle widget.
827- // The endpoint is at the bottom of the selection rect, which is also at the
828- // bottom of the line of text.
829- // Try to shift the top of the handle to the selection endpoint by the dy of
830- // the handle's anchor.
831- final double handleAnchorDy = selectionControls! .getHandleAnchor (type, renderObject.preferredLineHeight).dy;
832-
833- // Try to shift the selection endpoint to the center of the correct line by
834- // using half of the line height.
835- final double halfPreferredLineHeight = renderObject.preferredLineHeight / 2 ;
836-
837- // The x offset is accurate, so we only need to adjust the y position.
838- final double offsetYFromHandleToTextPosition = handleAnchorDy - halfHandleHeight - halfPreferredLineHeight;
839- return Offset (0.0 , offsetYFromHandleToTextPosition);
840- }
841-
842868 void _handleSelectionHandleChanged (TextSelection newSelection, {required bool isEnd}) {
843869 final TextPosition textPosition = isEnd ? newSelection.extent : newSelection.base ;
844870 selectionDelegate.userUpdateTextEditingValue (
0 commit comments