Take paint offset into account for inline children hit test in Editable (#131675)
This commit is contained in:
parent
545ecd29f2
commit
bc4cacac0d
@ -1916,9 +1916,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
@override
|
||||
@protected
|
||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
||||
final Offset effectivePosition = position - _paintOffset;
|
||||
final InlineSpan? textSpan = _textPainter.text;
|
||||
if (textSpan != null) {
|
||||
final Offset effectivePosition = position - _paintOffset;
|
||||
final TextPosition textPosition = _textPainter.getPositionForOffset(effectivePosition);
|
||||
final Object? span = textSpan.getSpanForPosition(textPosition);
|
||||
if (span is HitTestTarget) {
|
||||
@ -1926,7 +1926,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return hitTestInlineChildren(result, position);
|
||||
return hitTestInlineChildren(result, effectivePosition);
|
||||
}
|
||||
|
||||
late TapGestureRecognizer _tap;
|
||||
|
@ -1721,6 +1721,89 @@ void main() {
|
||||
editable.hitTest(result, position: const Offset(5.0, 15.0));
|
||||
expect(result.path, hasLength(0));
|
||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
|
||||
|
||||
test('hits correct WidgetSpan when scrolled', () {
|
||||
final String text = '${"\n" * 10}test';
|
||||
final TextSelectionDelegate delegate = _FakeEditableTextState()
|
||||
..textEditingValue = TextEditingValue(
|
||||
text: text,
|
||||
selection: const TextSelection.collapsed(offset: 13),
|
||||
);
|
||||
final List<RenderBox> renderBoxes = <RenderBox>[
|
||||
RenderParagraph(const TextSpan(text: 'a'), textDirection: TextDirection.ltr),
|
||||
RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
|
||||
RenderParagraph(const TextSpan(text: 'c'), textDirection: TextDirection.ltr),
|
||||
];
|
||||
final RenderEditable editable = RenderEditable(
|
||||
maxLines: null,
|
||||
text: TextSpan(
|
||||
style: const TextStyle(height: 1.0, fontSize: 10.0),
|
||||
children: <InlineSpan>[
|
||||
TextSpan(text: text),
|
||||
const WidgetSpan(child: Text('a')),
|
||||
const TextSpan(children: <InlineSpan>[
|
||||
WidgetSpan(child: Text('b')),
|
||||
WidgetSpan(child: Text('c')),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
startHandleLayerLink: LayerLink(),
|
||||
endHandleLayerLink: LayerLink(),
|
||||
textDirection: TextDirection.ltr,
|
||||
offset: ViewportOffset.fixed(100.0), // equal to the height of the 10 empty lines
|
||||
textSelectionDelegate: delegate,
|
||||
selection: const TextSelection.collapsed(
|
||||
offset: 0,
|
||||
),
|
||||
children: renderBoxes,
|
||||
);
|
||||
_applyParentData(renderBoxes, editable.text!);
|
||||
layout(editable, constraints: BoxConstraints.loose(const Size(500.0, 500.0)));
|
||||
// Prepare for painting after layout.
|
||||
pumpFrame(phase: EnginePhase.compositingBits);
|
||||
BoxHitTestResult result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: Offset.zero);
|
||||
// We expect two hit test entries in the path because the RenderEditable
|
||||
// will add itself as well.
|
||||
expect(result.path, hasLength(2));
|
||||
HitTestTarget target = result.path.first.target;
|
||||
expect(target, isA<TextSpan>());
|
||||
expect((target as TextSpan).text, text);
|
||||
// Only testing the RenderEditable entry here once, not anymore below.
|
||||
expect(result.path.last.target, isA<RenderEditable>());
|
||||
result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: const Offset(15.0, 0.0));
|
||||
expect(result.path, hasLength(2));
|
||||
target = result.path.first.target;
|
||||
expect(target, isA<TextSpan>());
|
||||
expect((target as TextSpan).text, text);
|
||||
|
||||
result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: const Offset(41.0, 0.0));
|
||||
expect(result.path, hasLength(3));
|
||||
target = result.path.first.target;
|
||||
expect(target, isA<TextSpan>());
|
||||
expect((target as TextSpan).text, 'a');
|
||||
|
||||
result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: const Offset(55.0, 0.0));
|
||||
expect(result.path, hasLength(3));
|
||||
target = result.path.first.target;
|
||||
expect(target, isA<TextSpan>());
|
||||
expect((target as TextSpan).text, 'b');
|
||||
|
||||
result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: const Offset(69.0, 5.0));
|
||||
expect(result.path, hasLength(3));
|
||||
target = result.path.first.target;
|
||||
expect(target, isA<TextSpan>());
|
||||
expect((target as TextSpan).text, 'c');
|
||||
|
||||
result = BoxHitTestResult();
|
||||
editable.hitTest(result, position: const Offset(5.0, 15.0));
|
||||
expect(result.path, hasLength(2));
|
||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
|
||||
});
|
||||
|
||||
test('does not skip TextPainter.layout because of invalid cache', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user