@@ -424,28 +424,36 @@ static BOOL IsApproximatelyEqual(float x, float y, float delta) {
424424 return fabsf (x - y) <= delta;
425425}
426426
427+ // This is a helper function for floating cursor selection logic to determine which text
428+ // position is closer to a point.
427429// Checks whether point should be considered closer to selectionRect compared to
428430// otherSelectionRect.
429431//
430- // Uses the leading-center point on selectionRect and otherSelectionRect to compare.
432+ // If `useTrailingBoundaryOfSelectionRect` is not set, it uses the leading-center point
433+ // on selectionRect and otherSelectionRect to compare.
431434// For left-to-right text, this means the left-center point, and for right-to-left text,
432435// this means the right-center point.
433436//
434437// If useTrailingBoundaryOfSelectionRect is set, the trailing-center point on selectionRect
435- // will be used instead of the leading-center point.
438+ // will be used instead of the leading-center point, while leading-center point is still used
439+ // for otherSelectionRect.
436440//
437441// This uses special (empirically determined using a 1st gen iPad pro, 9.7" model running
438442// iOS 14.7.1) logic for determining the closer rect, rather than a simple distance calculation.
439- // First, the closer vertical distance is determined. Within the closest y distance, if the point is
440- // above the bottom of the closest rect, the x distance will be minimized; however, if the point is
441- // below the bottom of the rect, the x value will be maximized.
443+ // - First, the rect with closer y distance wins.
444+ // - Otherwise (same y distance):
445+ // - If the point is above bottom of the rect, the rect boundary with closer x distance wins.
446+ // - Otherwise (point is below bottom of the rect), the rect boundary with farthest x wins.
447+ // This is because when the point is below the bottom line of text, we want to select the
448+ // whole line of text, so we mark the farthest rect as closest.
442449static BOOL IsSelectionRectBoundaryCloserToPoint (CGPoint point,
443450 CGRect selectionRect,
444451 BOOL selectionRectIsRTL,
445452 BOOL useTrailingBoundaryOfSelectionRect,
446453 CGRect otherSelectionRect,
447454 BOOL otherSelectionRectIsRTL,
448455 CGFloat verticalPrecision) {
456+ // The point is inside the selectionRect's corresponding half-rect area.
449457 if (CGRectContainsPoint (
450458 CGRectMake (
451459 selectionRect.origin .x + ((useTrailingBoundaryOfSelectionRect ^ selectionRectIsRTL)
@@ -455,13 +463,15 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
455463 point)) {
456464 return YES ;
457465 }
466+ // pointForSelectionRect is either leading-center or trailing-center point of selectionRect.
458467 CGPoint pointForSelectionRect = CGPointMake (
459468 selectionRect.origin .x +
460469 (selectionRectIsRTL ^ useTrailingBoundaryOfSelectionRect ? selectionRect.size .width : 0 ),
461470 selectionRect.origin .y + selectionRect.size .height * 0.5 );
462471 float yDist = fabs (pointForSelectionRect.y - point.y );
463472 float xDist = fabs (pointForSelectionRect.x - point.x );
464473
474+ // pointForOtherSelectionRect is the leading-center point of otherSelectionRect.
465475 CGPoint pointForOtherSelectionRect = CGPointMake (
466476 otherSelectionRect.origin .x + (otherSelectionRectIsRTL ? otherSelectionRect.size .width : 0 ),
467477 otherSelectionRect.origin .y + otherSelectionRect.size .height * 0.5 );
@@ -476,6 +486,7 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
476486 BOOL isAboveBottomOfLine = point.y <= selectionRect.origin .y + selectionRect.size .height ;
477487 BOOL isCloserHorizontally = xDist < xDistOther;
478488 BOOL isBelowBottomOfLine = point.y > selectionRect.origin .y + selectionRect.size .height ;
489+ // Is "farther away", or is closer to the end of the text line.
479490 BOOL isFarther;
480491 if (selectionRectIsRTL) {
481492 isFarther = selectionRect.origin .x < otherSelectionRect.origin .x ;
@@ -1669,7 +1680,7 @@ - (BOOL)isRTLAtPosition:(NSUInteger)position {
16691680- (CGRect)caretRectForPosition : (UITextPosition*)position {
16701681 NSInteger index = ((FlutterTextPosition*)position).index ;
16711682 UITextStorageDirection affinity = ((FlutterTextPosition*)position).affinity ;
1672- // Get the bounds of the characters before and after the requested caret position.
1683+ // Get the selectionRect of the characters before and after the requested caret position.
16731684 NSArray <UITextSelectionRect*>* rects = [self
16741685 selectionRectsForRange: [FlutterTextRange
16751686 rangeWithNSRange: fml: :RangeForCharactersInRange (
@@ -1696,6 +1707,7 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
16961707 characterAfterCaret.size .height );
16971708 }
16981709 } else if (rects.count == 2 && affinity == UITextStorageDirectionForward) {
1710+ // There are characters before and after the caret, with forward direction affinity.
16991711 // It's better to use the character after the caret.
17001712 CGRect characterAfterCaret = rects[1 ].rect ;
17011713 // Return a zero-width rectangle along the upstream edge of the character after the caret
@@ -1708,9 +1720,13 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
17081720 characterAfterCaret.size .height );
17091721 }
17101722 }
1723+
1724+ // Covers 2 remaining cases:
1725+ // 1. there are characters before and after the caret, with backward direction affinity.
1726+ // 2. there is only 1 character before the caret (caret is at the end of text).
1727+ // For both cases, return a zero-width rectangle along the downstream edge of the character
1728+ // before the caret position.
17111729 CGRect characterBeforeCaret = rects[0 ].rect;
1712- // Return a zero-width rectangle along the downstream edge of the character before the caret
1713- // position.
17141730 if ([self isRTLAtPosition: index - 1 ]) {
17151731 return CGRectMake (characterBeforeCaret.origin .x , characterBeforeCaret.origin .y , 0 ,
17161732 characterBeforeCaret.size .height );
@@ -1787,6 +1803,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang
17871803 // Allow further vertical deviation and base more of the decision on horizontal comparison.
17881804 CGFloat verticalPrecision = _isFloatingCursorActive ? 10 : 1 ;
17891805
1806+ // Find the selectionRect with a leading-center point that is closest to a given point.
17901807 BOOL isFirst = YES ;
17911808 NSUInteger _closestRectIndex = 0 ;
17921809 for (NSUInteger i = 0 ; i < [_selectionRects count ]; i++) {
@@ -1807,7 +1824,10 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang
18071824 [FlutterTextPosition positionWithIndex: _selectionRects[_closestRectIndex].position
18081825 affinity: UITextStorageDirectionForward];
18091826
1810- // Check if the far side of the closest rect is a better fit (tapping end of line)
1827+ // Check if the far side of the closest rect is a better fit (e.g. tapping end of line)
1828+ // Cannot simply check the _closestRectIndex result from the previous for loop due to RTL
1829+ // writing direction and the gaps between selectionRects. So we also need to consider
1830+ // the adjacent selectionRects to refine _closestRectIndex.
18111831 for (NSUInteger i = MAX (0 , _closestRectIndex - 1 );
18121832 i < MIN (_closestRectIndex + 2 , [_selectionRects count ]); i++) {
18131833 NSUInteger position = _selectionRects[i].position + 1 ;
@@ -1839,10 +1859,10 @@ - (void)beginFloatingCursorAtPoint:(CGPoint)point {
18391859 // width >= 0 ? point.x.clamp(boundingBox.left, boundingBox.right) : point.x,
18401860 // height >= 0 ? point.y.clamp(boundingBox.top, boundingBox.bottom) : point.y,
18411861 // )
1842- // where
1843- // point = keyboardPanGestureRecognizer.translationInView(textInputView) +
1844- // caretRectForPosition boundingBox = self.convertRect(bounds, fromView:textInputView)
1845- // bounds = self._selectionClipRect ?? self.bounds
1862+ // where
1863+ // point = keyboardPanGestureRecognizer.translationInView(textInputView) + caretRectForPosition
1864+ // boundingBox = self.convertRect(bounds, fromView:textInputView)
1865+ // bounds = self._selectionClipRect ?? self.bounds
18461866 //
18471867 // It seems impossible to use a negative "width" or "height", as the "convertRect"
18481868 // call always turns a CGRect's negative dimensions into non-negative values, e.g.,
0 commit comments