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
|
@override
|
||||||
@protected
|
@protected
|
||||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
||||||
|
final Offset effectivePosition = position - _paintOffset;
|
||||||
final InlineSpan? textSpan = _textPainter.text;
|
final InlineSpan? textSpan = _textPainter.text;
|
||||||
if (textSpan != null) {
|
if (textSpan != null) {
|
||||||
final Offset effectivePosition = position - _paintOffset;
|
|
||||||
final TextPosition textPosition = _textPainter.getPositionForOffset(effectivePosition);
|
final TextPosition textPosition = _textPainter.getPositionForOffset(effectivePosition);
|
||||||
final Object? span = textSpan.getSpanForPosition(textPosition);
|
final Object? span = textSpan.getSpanForPosition(textPosition);
|
||||||
if (span is HitTestTarget) {
|
if (span is HitTestTarget) {
|
||||||
@ -1926,7 +1926,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hitTestInlineChildren(result, position);
|
return hitTestInlineChildren(result, effectivePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
late TapGestureRecognizer _tap;
|
late TapGestureRecognizer _tap;
|
||||||
|
@ -1721,6 +1721,89 @@ void main() {
|
|||||||
editable.hitTest(result, position: const Offset(5.0, 15.0));
|
editable.hitTest(result, position: const Offset(5.0, 15.0));
|
||||||
expect(result.path, hasLength(0));
|
expect(result.path, hasLength(0));
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
|
}, 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', () {
|
test('does not skip TextPainter.layout because of invalid cache', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user