Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SuperEditor][SuperTextField][iOS] Make caret word snapping less aggressive (Resolves #2152) #2459

Merged
merged 7 commits into from
Jan 3, 2025

Conversation

angelosilvestre
Copy link
Collaborator

[SuperEditor][SuperTextField][iOS] Make caret word snapping less aggressive. Resolves #2152

On iOS, the caret snaps to the edges of a word. However, this shouldn't happen when the user taps at the caret position.

This PR changes SuperEditor and SuperTextField to avoid snapping the caret when tapping at the caret.

Some required adjustments:

  • The check for a tap over the caret was using the snapped position instead of the tap position. Modified to use the exact tap position.
  • The check for a tap over the caret was expanding the caret 24px in all directions. This causes an issue, because tapping on a line below (or up) can be interpreted as a tap on the caret. Modified to expand only horizontally.

We still have a problem in SuperEditor regarding showing the toolbar when tapping at the caret. We have this code:

if (didTapOnExistingSelection && _isKeyboardOpen) {
  // Toggle the toolbar display when the user taps on the collapsed caret,
  // or on top of an existing selection.
  //
  // But we only do this when the keyboard is already open. This is because
  // we don't want to show the toolbar when the user taps simply to open
  // the keyboard. That would feel unintentional, like a bug.
  _controlsController!.toggleToolbar();
} else {
  // The user tapped somewhere else in the document. Hide the toolbar.
  _controlsController!.hideToolbar();
}

However, _isKeyboardOpen uses the bottom view insets. This doesn't work if there are any Scaffolds in the tree without resizeToAvoidBottomInset set to false. Maybe we can check for an ancestor KeyboardScaffoldSafeArea and if we don't find one, we don't do the _isKeyboardOpen check.

@angelosilvestre
Copy link
Collaborator Author

@matthew-carroll

There are three failing tests that I'm not sure what to do:

  • SuperEditor robot taps to place caret at a non-linebreak offset with different affinities (on iOS)
  • SuperEditor text affinity upstream and downstream positions render differently at a line break (on iOS)
  • SuperEditor text affinity upstream and downstream positions render the same if not at a line break (on iOS)

All of these tests place the caret at a specific position, then try to place again in the same position, but with different affinity. So, the taps are being considered to be over the caret, and we are not changing the selection.

On Android, it seems we change the selection even when tapping at the caret.

Should these tests attempt to clear the selection before trying to set the new selection?

@angelosilvestre
Copy link
Collaborator Author

@matthew-carroll I modified also "SuperEditor text affinity upstream and downstream positions render the same if not at a line break (on iOS)" because it also uses the same document position with different affinities.

If this isn't ideal, then we probably should modify the checks to instead of looking at the document position look at the current caret rect.

import 'package:super_editor/src/test/test_globals.dart';
import 'package:super_text_layout/super_text_layout.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
// Disable indeterminate animations
BlinkController.indeterminateAnimationsEnabled = false;

// Disable iOS selection heuristics.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please mention what those heuristics are. The reader of this probably has no idea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

)),
);

// Press and drag the caret to "con|sectetur".
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check all of these comments. For example, we say we're dragging the caret, but we show it in the same place. Down below we say we moved the caret...to the same place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are not showing it in the same place, we are dragging from offset 39 up to 32.

// con|sectetur
//     <------|

@@ -214,6 +217,63 @@ void main() {
expect(SuperEditorInspector.isCaretVisible(), true);
});

testWidgetsOnIos("keeps current selection when tapping on caret", (tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

Between the name of this test, and the dragging happening in the test, it's not clear to me what we're really trying to demonstrate, or why....

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm dragging the caret because that's the only way to simulate an user interaction to place the caret at a specific position that's not a word boundary.

Without dragging we need to manually issue a selection change request.

await tester
.createDocument()
// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
.withSingleParagraph()
.simulateSoftwareKeyboardInsets(true)
.useIosSelectionHeuristics(useIosSelectionHeuristics)
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is configured on our editor configurator, then why do we also have it defined statically?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It defaults to false in the configurator. Here it allows tests to enable the heuristics.

const TextSelection.collapsed(offset: -1),
);

// Tap at "ips|um" to place the caret at the end of the word.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please mention how it ends up at the end of the word. When reading this comment in isolated, it looks like the first half of the comment contradicts the second half of the comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

@@ -53,6 +54,56 @@ void main() {
// Ensure that the text field toolbar disappeared.
expect(find.byType(IOSTextEditingFloatingToolbar), findsNothing);
});

testWidgetsOnIos("keeps current selection when tapping on caret", (tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the other test - is this really just about tapping on a caret? Because dragging doesn't seem necessary for that specific check. Seems like we're missing some additional important details about the goal.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Dragging is the only user interaction that can place the caret at the middle of a word when the selection heuristics are enabled. Should I mention this in the comment?

@angelosilvestre
Copy link
Collaborator Author

@matthew-carroll I pushed a change that I forgot to push before.

Copy link
Contributor

@matthew-carroll matthew-carroll left a comment

Choose a reason for hiding this comment

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

LGTM

@matthew-carroll matthew-carroll merged commit 3d5700f into main Jan 3, 2025
16 checks passed
@matthew-carroll matthew-carroll deleted the 2152_caret-snapping branch January 3, 2025 02:34
github-actions bot pushed a commit that referenced this pull request Jan 3, 2025
matthew-carroll pushed a commit that referenced this pull request Jan 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[SuperEditor][SuperTextField][iOS] - caret word snapping is too aggressive
2 participants