Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f471b37

Browse files
authored
[floating_cursor_selection]add some comments on the tricky part (#42136)
Many parts of the floating cursor selection feature is pretty tricky. Some took me a while to figure out. So I added some comments to explain a bit for future readers. *List which issues are fixed by this PR. You must list at least one issue.* flutter/flutter#30476 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent a0ea4d2 commit f471b37

File tree

1 file changed

+33
-13
lines changed

1 file changed

+33
-13
lines changed

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
442449
static 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

Comments
 (0)