Skip to content

Commit bc4caca

Browse files
authored
Take paint offset into account for inline children hit test in Editable (flutter#131675)
1 parent 545ecd2 commit bc4caca

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

packages/flutter/lib/src/rendering/editable.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,17 +1916,17 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
19161916
@override
19171917
@protected
19181918
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
1919+
final Offset effectivePosition = position - _paintOffset;
19191920
final InlineSpan? textSpan = _textPainter.text;
19201921
if (textSpan != null) {
1921-
final Offset effectivePosition = position - _paintOffset;
19221922
final TextPosition textPosition = _textPainter.getPositionForOffset(effectivePosition);
19231923
final Object? span = textSpan.getSpanForPosition(textPosition);
19241924
if (span is HitTestTarget) {
19251925
result.add(HitTestEntry(span));
19261926
return true;
19271927
}
19281928
}
1929-
return hitTestInlineChildren(result, position);
1929+
return hitTestInlineChildren(result, effectivePosition);
19301930
}
19311931

19321932
late TapGestureRecognizer _tap;

packages/flutter/test/rendering/editable_test.dart

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,89 @@ void main() {
17211721
editable.hitTest(result, position: const Offset(5.0, 15.0));
17221722
expect(result.path, hasLength(0));
17231723
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
1724+
1725+
test('hits correct WidgetSpan when scrolled', () {
1726+
final String text = '${"\n" * 10}test';
1727+
final TextSelectionDelegate delegate = _FakeEditableTextState()
1728+
..textEditingValue = TextEditingValue(
1729+
text: text,
1730+
selection: const TextSelection.collapsed(offset: 13),
1731+
);
1732+
final List<RenderBox> renderBoxes = <RenderBox>[
1733+
RenderParagraph(const TextSpan(text: 'a'), textDirection: TextDirection.ltr),
1734+
RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
1735+
RenderParagraph(const TextSpan(text: 'c'), textDirection: TextDirection.ltr),
1736+
];
1737+
final RenderEditable editable = RenderEditable(
1738+
maxLines: null,
1739+
text: TextSpan(
1740+
style: const TextStyle(height: 1.0, fontSize: 10.0),
1741+
children: <InlineSpan>[
1742+
TextSpan(text: text),
1743+
const WidgetSpan(child: Text('a')),
1744+
const TextSpan(children: <InlineSpan>[
1745+
WidgetSpan(child: Text('b')),
1746+
WidgetSpan(child: Text('c')),
1747+
],
1748+
),
1749+
],
1750+
),
1751+
startHandleLayerLink: LayerLink(),
1752+
endHandleLayerLink: LayerLink(),
1753+
textDirection: TextDirection.ltr,
1754+
offset: ViewportOffset.fixed(100.0), // equal to the height of the 10 empty lines
1755+
textSelectionDelegate: delegate,
1756+
selection: const TextSelection.collapsed(
1757+
offset: 0,
1758+
),
1759+
children: renderBoxes,
1760+
);
1761+
_applyParentData(renderBoxes, editable.text!);
1762+
layout(editable, constraints: BoxConstraints.loose(const Size(500.0, 500.0)));
1763+
// Prepare for painting after layout.
1764+
pumpFrame(phase: EnginePhase.compositingBits);
1765+
BoxHitTestResult result = BoxHitTestResult();
1766+
editable.hitTest(result, position: Offset.zero);
1767+
// We expect two hit test entries in the path because the RenderEditable
1768+
// will add itself as well.
1769+
expect(result.path, hasLength(2));
1770+
HitTestTarget target = result.path.first.target;
1771+
expect(target, isA<TextSpan>());
1772+
expect((target as TextSpan).text, text);
1773+
// Only testing the RenderEditable entry here once, not anymore below.
1774+
expect(result.path.last.target, isA<RenderEditable>());
1775+
result = BoxHitTestResult();
1776+
editable.hitTest(result, position: const Offset(15.0, 0.0));
1777+
expect(result.path, hasLength(2));
1778+
target = result.path.first.target;
1779+
expect(target, isA<TextSpan>());
1780+
expect((target as TextSpan).text, text);
1781+
1782+
result = BoxHitTestResult();
1783+
editable.hitTest(result, position: const Offset(41.0, 0.0));
1784+
expect(result.path, hasLength(3));
1785+
target = result.path.first.target;
1786+
expect(target, isA<TextSpan>());
1787+
expect((target as TextSpan).text, 'a');
1788+
1789+
result = BoxHitTestResult();
1790+
editable.hitTest(result, position: const Offset(55.0, 0.0));
1791+
expect(result.path, hasLength(3));
1792+
target = result.path.first.target;
1793+
expect(target, isA<TextSpan>());
1794+
expect((target as TextSpan).text, 'b');
1795+
1796+
result = BoxHitTestResult();
1797+
editable.hitTest(result, position: const Offset(69.0, 5.0));
1798+
expect(result.path, hasLength(3));
1799+
target = result.path.first.target;
1800+
expect(target, isA<TextSpan>());
1801+
expect((target as TextSpan).text, 'c');
1802+
1803+
result = BoxHitTestResult();
1804+
editable.hitTest(result, position: const Offset(5.0, 15.0));
1805+
expect(result.path, hasLength(2));
1806+
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
17241807
});
17251808

17261809
test('does not skip TextPainter.layout because of invalid cache', () {

0 commit comments

Comments
 (0)