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

Make TextSpan hit testing precise. #139717

Merged
merged 18 commits into from
Dec 20, 2023

Conversation

LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Dec 7, 2023

Fixes #131435, #104594, #43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing TextPainter.getPositionForOffset always returns the closest TextPosition, even when the given offset is far away from the text.

The new TextPaintes method tells you the layout bounds (width = letterspacing / 2 + x_advance + letterspacing / 2, height = font ascent + font descent) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

  1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference.

This is a breaking change. It also introduces a new finder and a new method WidgetTester.tapOnText: await tester.tapOnText('string to match') for ease of migration.

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, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. f: scrolling Viewports, list views, slivers, etc. labels Dec 7, 2023
@github-actions github-actions bot added the a: tests "flutter test", flutter_test, or one of our tests label Dec 8, 2023
@@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
Copy link
Member

Choose a reason for hiding this comment

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

Currently, flutter_test doesn't directly depend on package:collection (it's just a transitive dependency). We'd need to update the pubspec to reflect this. What is this using from package:collection? There have been some efforts (albeit currently stalled) to remove pkg:collection as a dependency from flutter, this would move us in the opposite direction.

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 used first where or null. Turned that into a for loop.

packages/flutter/lib/src/painting/text_painter.dart Outdated Show resolved Hide resolved
/// Returns the position within the text for the given pixel offset.
/// Returns the [GlyphInfo] of the glyph closest to the given `offset` in the
/// paragraph coordinate system, or null if the glyph is not in the visible
/// range.
Copy link
Member

Choose a reason for hiding this comment

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

for my own understanding (and maybe to clarify in the docs): What is the "visible range"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I copied the documentation from another method and that doesn't make sense in this context.
This method returns null if the text is empty, or is entirely clipped or ellipsized. I'll fix that in the engine PR.

///
/// This method can be used to implement per-glyph hit-testing. The returned
/// [GlyphInfo] can help determine whether the given `offset` directly hits a
/// glyph in the paragraph.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe clarify in the doc: What do I need to do with the GlyphInfo to figure out whether the glyph was directly hit or not? (e.g. check that offset is inside graphemeClusterLayoutBounds)

packages/flutter/lib/src/rendering/editable.dart Outdated Show resolved Hide resolved
Comment on lines 1036 to 1038
/// This method currently only works on static text widgets ([Text] or
/// [RichText] for example) that use [RenderParagraph] under the hood. It
/// currently does not support finding substrings in [TextField]s or
Copy link
Member

Choose a reason for hiding this comment

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

Remove the "currently"s in these two sentences?

"does not support" => will it throw if it find the substring in TextFields? Will it just not see that text at all? Maybe be a little more specific what "not supported" means.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now it skips RenderEditables because it's not need for my migration. Removed the paragraph because the limitation is the finder not this method.

/// throws a [FlutterError].
///
/// If the target substring contains more than one hit-testable [InlineSpan]s,
/// [tapOnText] taps on one of them, but does not guarantee which.
Copy link
Member

Choose a reason for hiding this comment

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

Huh? The first paragraph said it would hit the first one. This one says it doesn't guarantee which one. Can you unify these two statements? Also, if the "no guarantee" statement is the correct one: Why can we not guarantee which one gets hit? If we cannot define which one gets hit, should we instead throw in this case and make that illegal? Is it at least deterministic which gets hit?

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 want users to rely on this, the real order can be a bit hard to reason about because of bidi reordering and fallback fonts being used (the getBoxesForSelection method may return different TextBoxes, and combine adjacent TextBoxes into one).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the first paragraph.

/// [RichText] for example) that use [RenderParagraph] under the hood. It
/// currently does not support finding substrings in [TextField]s or
/// [SelectableText].
Future<void> tapOnText(finders.FinderBase<finders.TextRangeContext> textRangeFinder, {int? pointer, int buttons = kPrimaryButton }) {
Copy link
Member

Choose a reason for hiding this comment

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

Document the other parameters

packages/flutter_test/lib/src/controller.dart Outdated Show resolved Hide resolved
packages/flutter_test/lib/src/controller.dart Outdated Show resolved Hide resolved
auto-submit bot pushed a commit to flutter/engine that referenced this pull request Dec 13, 2023
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -1492,8 +1494,24 @@ class TextPainter {
? boxes
: boxes.map((TextBox box) => _shiftTextBox(box, offset)).toList(growable: false);
}
/// Returns the [GlyphInfo] of the glyph closest to the given `offset` in the
Copy link
Member

Choose a reason for hiding this comment

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

nit: add blank line before this line?

@@ -1492,8 +1494,24 @@ class TextPainter {
? boxes
: boxes.map((TextBox box) => _shiftTextBox(box, offset)).toList(growable: false);
}
/// Returns the [GlyphInfo] of the glyph closest to the given `offset` in the
/// paragraph coordinate system, or null if if the text is empty, or is
Copy link
Member

Choose a reason for hiding this comment

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

remove one of the "if"s?

Comment on lines 630 to 631
// This expect always fails.
expect(target, anyOf(isA<TextSpan>(), isA<RenderParagraph>()));
Copy link
Member

Choose a reason for hiding this comment

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

Why not use fail then to make it explicit that this is a failure case and provide a failure message?

https://main-api.flutter.dev/flutter/package-matcher_expect/fail.html


/// Helper function that returns 3 special Offsets within the given `box`, to
/// try performing hit-testing at.
Iterable<Offset> _testOffsetsForEachTextBox(TextBox box) {
Copy link
Member

Choose a reason for hiding this comment

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

Could we just hit test the center, similar to https://main-api.flutter.dev/flutter/flutter_test/WidgetController/tap.html?

If we do this, let's also mention it in the docs.

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 it's a bit hard to explain how the glyphs are grouped into boxes, if we want to mention hit testing the center of TextBoxes? If the specified font doesn't have glyphs for some characters in the substring then these glyphs will usually be in separate TextBoxes. The 3rd paragraph says the substring must not be obstructed or offscreen, does that work?

/// If the `skipOffstage` argument is true (the default), then this skips
/// static text inside widgets that are [Offstage], or that are from inactive
/// [Route]s.
///
Copy link
Member

Choose a reason for hiding this comment

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

Maybe document how overlapping substrings are handled.

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

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

LGTM, just some questions

return ui.GlyphInfo(rawGlyphInfo.graphemeClusterLayoutBounds.shift(cachedLayout.paintOffset), rawGlyphInfo.graphemeClusterCodeUnitRange, rawGlyphInfo.writingDirection);
}

/// Returns the closest position within the text for the given pixel offset.
TextPosition getPositionForOffset(Offset offset) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be replaced by getClosestGlyphForOffset?

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 believe so.

final InlineSpan? textSpan = _textPainter.text;
switch (textSpan?.getSpanForPosition(_textPainter.getPositionForOffset(effectivePosition))) {
final GlyphInfo? glyph = _textPainter.getClosestGlyphForOffset(effectivePosition);
// This works even with large letter-spacing and text justification, as
Copy link
Contributor

@chunhtai chunhtai Dec 14, 2023

Choose a reason for hiding this comment

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

reading this i am not sure what works mean. Do you mean it will miss when click in between character? or it would still hit the span. I think it is the latter, but would be good to be more clear

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 would still hit the span (same behavior as getSpanForPosition ). I'll update the comment.

switch (textSpan?.getSpanForPosition(_textPainter.getPositionForOffset(effectivePosition))) {
final GlyphInfo? glyph = _textPainter.getClosestGlyphForOffset(effectivePosition);
// This works even with large letter-spacing and text justification, as
// graphemeClusterLayoutBounds.width is the x layout advance to the next
Copy link
Contributor

@chunhtai chunhtai Dec 14, 2023

Choose a reason for hiding this comment

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

I can guess what x layout advance mean, but is it a term that commonly used? If not, we should probably explain what this mean

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 https://freetype.org/freetype2/docs/glyphs/glyphs-3.html it's called advance width. I can change to that if you prefer it over x layout advance?

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL, yes I think using advance width may be more clear

@auto-submit auto-submit bot merged commit ea5b972 into flutter:master Dec 20, 2023
68 checks passed
@LongCatIsLooong LongCatIsLooong deleted the textspan-hittesting branch December 20, 2023 03:29
@XilaiZhang XilaiZhang added the cp: beta cherry pick this pull request to beta release candidate branch label Dec 20, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 20, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 20, 2023
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Dec 20, 2023
flutter/flutter@0eb7881...da0cd69

2023-12-20 git@reb0.org Revert automated changes made to deprecated settings.gradle (plugins.each)  (flutter/flutter#140037)
2023-12-20 xilaizhang@google.com [github actions] change minimal example workflow to be manually dispatched (flutter/flutter#140435)
2023-12-20 31859944+LongCatIsLooong@users.noreply.github.com Make `TextSpan` hit testing precise. (flutter/flutter#139717)
2023-12-20 xilaizhang@google.com [github actions] add minimal workflow to test token (flutter/flutter#140363)
2023-12-20 engine-flutter-autoroll@skia.org Roll Flutter Engine from 5279873a8635 to c70f0a495ace (2 revisions) (flutter/flutter#140431)
2023-12-20 engine-flutter-autoroll@skia.org Roll Flutter Engine from df1593e96a6b to 5279873a8635 (2 revisions) (flutter/flutter#140428)
2023-12-20 christopherfujino@gmail.com [flutter_tools] handle FileSystemException trying to delete temp directory from core_devices.dart (flutter/flutter#140415)
2023-12-19 zanderso@users.noreply.github.com Move hybrid_android_views_integration_test back to Moto G4 (flutter/flutter#140421)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3b156c8ce9bd to df1593e96a6b (2 revisions) (flutter/flutter#140422)
2023-12-19 72284940+feduke-nukem@users.noreply.github.com Added onEnd callback into AnimatedSize (flutter/flutter#139859)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3f45f9db4471 to 3b156c8ce9bd (1 revision) (flutter/flutter#140413)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 187334c39b44 to 3f45f9db4471 (3 revisions) (flutter/flutter#140412)
2023-12-19 15619084+vashworth@users.noreply.github.com Remove workarounds for `plugin_lint_mac` needed for older version of Cocoapods (flutter/flutter#140395)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from b87a782ce3a3 to 187334c39b44 (2 revisions) (flutter/flutter#140398)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 1d5a141917fa to b87a782ce3a3 (1 revision) (flutter/flutter#140390)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC bmparr@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

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

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@LongCatIsLooong LongCatIsLooong added the revert Autorevert PR (with "Reason for revert:" comment) label Dec 20, 2023
auto-submit bot pushed a commit that referenced this pull request Dec 20, 2023
@auto-submit auto-submit bot removed the revert Autorevert PR (with "Reason for revert:" comment) label Dec 20, 2023
auto-submit bot added a commit that referenced this pull request Dec 20, 2023
Reverts #139717
Initiated by: LongCatIsLooong
This change reverts the following previous change:
Original Description:
Fixes #131435, #104594, #43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
auto-submit bot pushed a commit that referenced this pull request Dec 20, 2023
Extracted from #139717 as-is. Landing this change first so we can avoid doing a g3fix.
ChopinDavid pushed a commit to wwt/flutter-packages that referenced this pull request Dec 28, 2023
…r#5729)

flutter/flutter@0eb7881...da0cd69

2023-12-20 git@reb0.org Revert automated changes made to deprecated settings.gradle (plugins.each)  (flutter/flutter#140037)
2023-12-20 xilaizhang@google.com [github actions] change minimal example workflow to be manually dispatched (flutter/flutter#140435)
2023-12-20 31859944+LongCatIsLooong@users.noreply.github.com Make `TextSpan` hit testing precise. (flutter/flutter#139717)
2023-12-20 xilaizhang@google.com [github actions] add minimal workflow to test token (flutter/flutter#140363)
2023-12-20 engine-flutter-autoroll@skia.org Roll Flutter Engine from 5279873a8635 to c70f0a495ace (2 revisions) (flutter/flutter#140431)
2023-12-20 engine-flutter-autoroll@skia.org Roll Flutter Engine from df1593e96a6b to 5279873a8635 (2 revisions) (flutter/flutter#140428)
2023-12-20 christopherfujino@gmail.com [flutter_tools] handle FileSystemException trying to delete temp directory from core_devices.dart (flutter/flutter#140415)
2023-12-19 zanderso@users.noreply.github.com Move hybrid_android_views_integration_test back to Moto G4 (flutter/flutter#140421)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3b156c8ce9bd to df1593e96a6b (2 revisions) (flutter/flutter#140422)
2023-12-19 72284940+feduke-nukem@users.noreply.github.com Added onEnd callback into AnimatedSize (flutter/flutter#139859)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3f45f9db4471 to 3b156c8ce9bd (1 revision) (flutter/flutter#140413)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 187334c39b44 to 3f45f9db4471 (3 revisions) (flutter/flutter#140412)
2023-12-19 15619084+vashworth@users.noreply.github.com Remove workarounds for `plugin_lint_mac` needed for older version of Cocoapods (flutter/flutter#140395)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from b87a782ce3a3 to 187334c39b44 (2 revisions) (flutter/flutter#140398)
2023-12-19 engine-flutter-autoroll@skia.org Roll Flutter Engine from 1d5a141917fa to b87a782ce3a3 (1 revision) (flutter/flutter#140390)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC bmparr@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

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

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
CoderDake pushed a commit to CoderDake/flutter that referenced this pull request Dec 28, 2023
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
CoderDake pushed a commit to CoderDake/flutter that referenced this pull request Dec 28, 2023
Reverts flutter#139717
Initiated by: LongCatIsLooong
This change reverts the following previous change:
Original Description:
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
CoderDake pushed a commit to CoderDake/flutter that referenced this pull request Dec 28, 2023
Extracted from flutter#139717 as-is. Landing this change first so we can avoid doing a g3fix.
victoreronmosele pushed a commit to victoreronmosele/flutter that referenced this pull request Jan 6, 2024
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
victoreronmosele pushed a commit to victoreronmosele/flutter that referenced this pull request Jan 6, 2024
Reverts flutter#139717
Initiated by: LongCatIsLooong
This change reverts the following previous change:
Original Description:
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
victoreronmosele pushed a commit to victoreronmosele/flutter that referenced this pull request Jan 6, 2024
Extracted from flutter#139717 as-is. Landing this change first so we can avoid doing a g3fix.
Michal-MK pushed a commit to Michal-MK/flutter that referenced this pull request Jan 8, 2024
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
Michal-MK pushed a commit to Michal-MK/flutter that referenced this pull request Jan 8, 2024
Reverts flutter#139717
Initiated by: LongCatIsLooong
This change reverts the following previous change:
Original Description:
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
Michal-MK pushed a commit to Michal-MK/flutter that referenced this pull request Jan 8, 2024
Extracted from flutter#139717 as-is. Landing this change first so we can avoid doing a g3fix.
Markzipan pushed a commit to Markzipan/flutter that referenced this pull request Jan 9, 2024
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
Markzipan pushed a commit to Markzipan/flutter that referenced this pull request Jan 9, 2024
Reverts flutter#139717
Initiated by: LongCatIsLooong
This change reverts the following previous change:
Original Description:
Fixes flutter#131435, flutter#104594, flutter#43400
Needs flutter/engine#48774 (to fix the web test failure).

Currently the method we use for text span hit testing `TextPainter.getPositionForOffset` always returns the closest `TextPosition`, even when the given offset is far away from the text. 

The new TextPaintes method tells you the layout bounds (`width =  letterspacing / 2 + x_advance + letterspacing / 2`, `height = font ascent + font descent`) of a character, the PR changes the hit testing implementation such that a TextSpan is only considered hit if the point-down event landed in one of it's character's layout bounds.

Potential issues:

1. In theory since the text is baseline aligned, we should use the max ascent and max descent of each character to calculate the height of the text span's hit-test region, in case some characters in the span have to fall back to a different font, but that will be slower and it typically doesn't make a huge difference. 

This is a breaking change. It also introduces a new finder and a new method `WidgetTester.tapOnText`: `await tester.tapOnText('string to match')` for ease of migration.
Markzipan pushed a commit to Markzipan/flutter that referenced this pull request Jan 9, 2024
Extracted from flutter#139717 as-is. Landing this change first so we can avoid doing a g3fix.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App cp: beta cherry pick this pull request to beta release candidate branch f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

InlineText's GestureDetector is always full-width
4 participants