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

iPhone floating cursor selection #36643

Merged
merged 12 commits into from
May 11, 2023
Merged

Conversation

moffatman
Copy link
Contributor

Floating cursor selection hasn't worked as we haven't been returning a real value for caretRectForPosition:. If we have the selection rectangles, we can do so.

Fixes #30476

Requires selection rects to be turned on for iPhone (flutter/flutter#113048)

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides].
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See [testing the engine] for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the [CLA].
  • All existing and new tests are passing.

@chinmaygarde
Copy link
Member

cc @cbracken @jmagman

@jmagman
Copy link
Member

jmagman commented Oct 19, 2022

@LongCatIsLooong might know the most about this #26486

@LongCatIsLooong
Copy link
Contributor

Is this implementing this feature?

A second force press leaves a selection anchor and then selects full words between the anchor and the current floating cursor position.

I think we should do that in the framework if possible. Is there a way that allows us to detect the second force press? Sending the bounding boxes of visible glyphs is going to be very expensive.

@moffatman
Copy link
Contributor Author

I haven't found any way to detect the second press, the keyboard just changes the selection based on the position of the floating cursor.

jmagman
jmagman previously approved these changes Nov 5, 2022
Copy link
Member

@jmagman jmagman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jmagman
Copy link
Member

jmagman commented Nov 9, 2022

@LongCatIsLooong do you have any more review requests or can this land?

@moffatman
Copy link
Contributor Author

@LongCatIsLooong We are already sending the rects since flutter/flutter#113048. I don't know how to look up benchmark results, maybe you can check the impact?

@LongCatIsLooong
Copy link
Contributor

LongCatIsLooong commented Nov 17, 2022

Sorry for the delayed response. I'm not aware of any existing benchmarks that cover this, the framework only sends new rects when the underlying text layout changes, or when the text field stops scrolling (and if the text is too long when the floating cursor moves close enough to an edge the text field may scroll, that could break the interaction here too), and I don't think the performance of entering text is currently measured/monitored (it's probably worth doing, I'll create an issue for that on a second thought, may not be a good idea since we have to interact with the OS's input system flutter/flutter#115595).

Is the 2nd force press event detectable via {begin/update/end}floatingCursor? If so I think we can replicate UIKit's behavior in the framework, like we do for regular gestures?

@moffatman
Copy link
Contributor Author

We can't find out about the second force press, the system just sends selection updates if used once we have the correct rectangles.

@LongCatIsLooong
Copy link
Contributor

LongCatIsLooong commented Nov 23, 2022

Ah I see it's using _UIKeyboardTextSelectionInteraction. What happens when you use the spacebar selection gesture to extend the selection outside of the text field's bounds? I think the framework currently stops sending rects if the text field starts to scroll.

@moffatman
Copy link
Contributor Author

Right now only the rects of the visible characters are sent so you can't scroll outside of the current viewport. I just tried always sending all the rects, it works surprisingly well, as since the rects aren't updated during scroll, they remain stable as UIKit changes the selection, which causes the framework to scroll. But it sometimes seems kind of glitchy, there may be some update leaking through from somewhere.

In general you wouldn't need to send all the rects to get scrolling to work, since the keyboard is only a fixed width. Probably it would be optimal to send the current visible characters as well as the same width of the view off of each edge. No idea about vertical scrolling though...

@chinmaygarde
Copy link
Member

Are we making progress on this? Can we land this perhaps and file a followup?

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation LGTM.

characterAfterCaret.size.height);
}
CGRect characterBeforeCaret = rects[0].rect;
// Return a zero-width rectangle along the right edge of the character before the caret position.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use the center instead? I think that would probably work better for RTL and Bidi text since we don't send the bidi info along with the selection rects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think centre would work correctly, it should stay where it is for LTR. But for RTL you're right it likely won't work properly. Maybe a better approach would be putting the rectangle midway between the closest two edges of the adjacent selectionRects to the cursor. Then we don't care about the paint direction v.s. semantic direction.

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Dec 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should stay where it is for LTR

Sorry I'm not sure how UIKit is going to use this for floating cursor, could you explain why that wouldn't work?

two edges of the adjacent selectionRects to the cursor.

If the position happens to be at a line break or bidi text boundary then the logically adjacent selection rects won't necessarily be visually adjacent right?

@jmagman
Copy link
Member

jmagman commented Dec 7, 2022

Almost ready to land, waiting on answer to last questions from @LongCatIsLooong

@moffatman
Copy link
Contributor Author

Upon revisiting this to check out linebreak / RTL text I think we will need some other framework changes first. Mainly to avoid existing floating cursor implementation from conflicting. Also working on engine improvements to handle various edge cases in caret rect positioning.

@jmagman
Copy link
Member

jmagman commented Dec 13, 2022

@moffatman let us know when this is ready to re-review.

@moffatman
Copy link
Contributor Author

@jmagman It's ready for re-review


- (CGRect)bounds {
return _isFloatingCursorActive ? kSpacePanBounds : super.bounds;
NSInteger index = ((FlutterTextPosition*)position).index;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit I think this is a NSUInteger

Copy link
Contributor Author

@moffatman moffatman Dec 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It have it as signed to do MAX(0, index - 1) later

// There is no character before the caret, so this will be the bounds of the character after the
// caret position.
CGRect characterAfterCaret = rects[0].rect;
// Return a zero-width rectangle 30% in from the left edge of the character after the caret
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work RTL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it did but I tested the wrong way, looking into changes now to handle proper RTL unicode chars.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a bug in Skia getGlyphPositionAtCoordinate in RTL, looking at that now

@moffatman
Copy link
Contributor Author

Here is the Skia change: https://skia-review.googlesource.com/c/skia/+/619838

Will also need another framework PR to send the text directionality of each selection rect

moffatman added 7 commits May 9, 2023 15:30
It fixes issues with characters of varying heights
Don't reduce the precision for pencil usage
Instead of the hack using the _selectionAffinity for the user's current
cursor position, which may not be the same as the one passed to caretRectForPosition.

Also optimize closestPositionToPoint, we don't need a second loop. The only possible
closer position is the far edge of the current closest position.
Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for making all these changes.

BOOL isFartherToRight =
selectionRect.origin.x + (checkRightBoundary ? selectionRect.size.width : 0) >
otherSelectionRect.origin.x;
BOOL isFarther;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe isCloserToTrailing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about "closer" to anything, just further in the direction of text layout.

if (_selectionRects[i].position >= start && _selectionRects[i].position <= end) {
if (_selectionRects[i].position >= start &&
(_selectionRects[i].position < end ||
(start == end && _selectionRects[i].position <= end))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this new condition for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the old behaviour, you would always get one more extra rect (since it was comparing <= end). Now, we are correctly using < end. But we still want to return the special rect (zero-width) if start == end instead of no rects. So we can use the old comparison <= end but only if we have start == end.

Text Range Old rects returned New rects returned
0..0 [0] with width zero [0] with width zero
0..1 [0], [1] [0]
0..2 [0], [1], [2] [0], [1]

And so on...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Looks like the old behavior is a bug then.

Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for making all these changes.

@moffatman moffatman added the autosubmit Merge PR when tree becomes green via auto submit App label May 11, 2023
@auto-submit auto-submit bot merged commit b120180 into flutter:main May 11, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 12, 2023
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request May 12, 2023
…126622)

flutter/engine@f38f46f...748ef96

2023-05-11 [email protected] Roll Skia from aed81125e6bc to ccec5093ca38 (10 revisions) (flutter/engine#41963)
2023-05-11 [email protected] Roll Fuchsia Mac SDK from 2tQjI0g3aDmjHAtMw... to MjcKzcsqMDkuRKopu... (flutter/engine#41960)
2023-05-11 [email protected] iPhone floating cursor selection (flutter/engine#36643)
2023-05-11 [email protected] Update README.md (flutter/engine#41953)
2023-05-11 [email protected] Roll Clang from 5344d8e10bb7 to 6d667d4b261e (flutter/engine#41949)
2023-05-11 [email protected] Use moved SkImage procs (flutter/engine#41947)
2023-05-11 [email protected] allow supplying custom gn args in gn wrapper (flutter/engine#41794)
2023-05-11 [email protected] fix: platform_dispatcher documentation typo (flutter/engine#41739)
2023-05-11 [email protected] Remove GN staging flag for save layer bounds (flutter/engine#41940)
2023-05-11 [email protected] switch MockTexture off of MockCanvas calls (flutter/engine#41906)
2023-05-11 [email protected] [Android] Lifecycle defaults to focused instead of unfocused (flutter/engine#41875)
2023-05-11 [email protected] Roll Skia from ccf73af6ca91 to aed81125e6bc (9 revisions) (flutter/engine#41946)
2023-05-11 [email protected] Clobber caches in licenses test. (flutter/engine#41942)

Also rolling transitive DEPS:
  fuchsia/sdk/core/mac-amd64 from 2tQjI0g3aDmj to MjcKzcsqMDku

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
CaseyHillers pushed a commit to CaseyHillers/flutter that referenced this pull request May 24, 2023
…lutter#126622)

flutter/engine@f38f46f...748ef96

2023-05-11 [email protected] Roll Skia from aed81125e6bc to ccec5093ca38 (10 revisions) (flutter/engine#41963)
2023-05-11 [email protected] Roll Fuchsia Mac SDK from 2tQjI0g3aDmjHAtMw... to MjcKzcsqMDkuRKopu... (flutter/engine#41960)
2023-05-11 [email protected] iPhone floating cursor selection (flutter/engine#36643)
2023-05-11 [email protected] Update README.md (flutter/engine#41953)
2023-05-11 [email protected] Roll Clang from 5344d8e10bb7 to 6d667d4b261e (flutter/engine#41949)
2023-05-11 [email protected] Use moved SkImage procs (flutter/engine#41947)
2023-05-11 [email protected] allow supplying custom gn args in gn wrapper (flutter/engine#41794)
2023-05-11 [email protected] fix: platform_dispatcher documentation typo (flutter/engine#41739)
2023-05-11 [email protected] Remove GN staging flag for save layer bounds (flutter/engine#41940)
2023-05-11 [email protected] switch MockTexture off of MockCanvas calls (flutter/engine#41906)
2023-05-11 [email protected] [Android] Lifecycle defaults to focused instead of unfocused (flutter/engine#41875)
2023-05-11 [email protected] Roll Skia from ccf73af6ca91 to aed81125e6bc (9 revisions) (flutter/engine#41946)
2023-05-11 [email protected] Clobber caches in licenses test. (flutter/engine#41942)

Also rolling transitive DEPS:
  fuchsia/sdk/core/mac-amd64 from 2tQjI0g3aDmj to MjcKzcsqMDku

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit bot pushed a commit that referenced this pull request Jun 12, 2023
The `isRTLAtPosition` method had a bug, it used `NSInteger max = [_selectionRects count]` instead of `NSInteger max = [_selectionRects count] - 1`. But I realized we don't even need the function any more, it was used in a few places in previous iterations of #36643, but in the only place remaining, we actually already have the selection rect and don't need to search for it by position.

Btw as an explanation of the crash, I guess there is some mismatch between code point and character count somewhere. UIKit was asking for `caretRectForPosition:2` when we only had 1 character. This could have only crashed when floating cursor selection was used, but actually when switching to CJK keyboard, UIKit turns out to use `caretRectForPosition` to calculate something about the composing rect.

Fixes flutter/flutter#128031
Jasguerrero pushed a commit that referenced this pull request Jun 16, 2023
The `isRTLAtPosition` method had a bug, it used `NSInteger max = [_selectionRects count]` instead of `NSInteger max = [_selectionRects count] - 1`. But I realized we don't even need the function any more, it was used in a few places in previous iterations of #36643, but in the only place remaining, we actually already have the selection rect and don't need to search for it by position.

Btw as an explanation of the crash, I guess there is some mismatch between code point and character count somewhere. UIKit was asking for `caretRectForPosition:2` when we only had 1 character. This could have only crashed when floating cursor selection was used, but actually when switching to CJK keyboard, UIKit turns out to use `caretRectForPosition` to calculate something about the composing rect.

Fixes flutter/flutter#128031
Jasguerrero pushed a commit to Jasguerrero/engine that referenced this pull request Jun 16, 2023
…42539)

The `isRTLAtPosition` method had a bug, it used `NSInteger max = [_selectionRects count]` instead of `NSInteger max = [_selectionRects count] - 1`. But I realized we don't even need the function any more, it was used in a few places in previous iterations of flutter#36643, but in the only place remaining, we actually already have the selection rect and don't need to search for it by position.

Btw as an explanation of the crash, I guess there is some mismatch between code point and character count somewhere. UIKit was asking for `caretRectForPosition:2` when we only had 1 character. This could have only crashed when floating cursor selection was used, but actually when switching to CJK keyboard, UIKit turns out to use `caretRectForPosition` to calculate something about the composing rect.

Fixes flutter/flutter#128031
Jasguerrero pushed a commit to Jasguerrero/engine that referenced this pull request Jun 17, 2023
…42540)

The `isRTLAtPosition` method had a bug, it used `NSInteger max = [_selectionRects count]` instead of `NSInteger max = [_selectionRects count] - 1`. But I realized we don't even need the function any more, it was used in a few places in previous iterations of flutter#36643, but in the only place remaining, we actually already have the selection rect and don't need to search for it by position.

Btw as an explanation of the crash, I guess there is some mismatch between code point and character count somewhere. UIKit was asking for `caretRectForPosition:2` when we only had 1 character. This could have only crashed when floating cursor selection was used, but actually when switching to CJK keyboard, UIKit turns out to use `caretRectForPosition` to calculate something about the composing rect.

Fixes flutter/flutter#128031
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App platform-ios
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support floating cursor double press on iOS
5 participants